View Issue Details

IDProjectCategoryView StatusLast Update
0001011OpenMPTGeneralpublic2021-12-25 18:20
ReporterSaga Musix Assigned Tomanx  
PrioritylowSeverityminorReproducibilityN/A
Status resolvedResolutionfixed 
Target VersionOpenMPT 1.30.01.00 / libopenmpt 0.6.0 (upgrade first)Fixed in VersionOpenMPT 1.30.01.00 / libopenmpt 0.6.0 (upgrade first) 
Summary0001011: Automatic update
Description

There are already automatic update notifications, but people are lazy, so in addition we should offer to show what's new and automatically install the new version. In case someone is skipping several versions, we might want to show the news for all skipped versions as well. Not the complete changelog of course, but just the most important items.

We will need a differently structured response from the server, and since picojson is already used for the Wine integration, using JSON for update information seems like a good idea. The update response could look something like this:

openmpt_versions = [
{
  "version":"1.28.01.00",
  "released":"2018-01-01",
  "announcement_url":"...",
  "download":
  {
    "win32":{url:"...","windows":"6.1","bits":32,"required_processor_features":{"sse2"}},
    "win32old":{url:"...","windows":"5.1","bits":32},
    "win64":{url:"...","windows":"6.1","bits":64},
    "win64old":{url:"...","windows":"5.1","bits":64}
  },
  "news":
  [
    "Fixed stuff",
    "Cool new feature"
  ]
},
(more skipped versions here, download links not needed)
]

The rationale for sending all download links to the client is that information about the user's operating system environment is sent to the server optionally. I think data avoidance is a goal worth striving for, so we should not force this information to be sent along.
This means that the client has to decide which version to download. Since support for some operating systems and processor combinations may cease to exist, we may also need to specify extra data the client should check for.
Suggested way of doing this:

  • Check if the current client configuration (e.g. "win32old") still exists and it meets the OS requirements specified by the server (e.g. 5.1). If version exists and minimum OS requirement is fulfilled, use this version.
  • Otherwise iterate through all other offered versions and check if any of them match the user's OS and current OpenMPT bitness (to avoid issues with plugin bitness). Suggest the user to switch to this new build variant.
  • If still no build variant is found, notify the user of this fact. Suggest visiting the OpenMPT website.

What else needs to be considered:

  • Is the user using an installer or zip version?
  • We need to decide if we need a new update checker entry point on the server or if we keep the old one. Since the update client already sends version information to the server, we can dynamically decide if we send old-style or new-style update information.
TagsNo tags attached.
Attached Files
updatedialog-v2.png (40,975 bytes)   
updatedialog-v2.png (40,975 bytes)   
update-statistics-v2.patch (34,400 bytes)   
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10676)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 28
+#define VER_MINORMINOR 29
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10676)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now",IDC_BUTTON1,222,84,54,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -881,7 +884,12 @@
     0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10676)
+++ mptrack/resource.h	(working copy)
@@ -973,6 +973,13 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1267,9 +1274,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        543
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2511
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10676)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,9 +331,16 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
 	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
@@ -644,6 +651,45 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,29))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10676)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,9 +817,16 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateSendGUID;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10676)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #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
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -84,12 +86,28 @@
 
 
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +122,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			return;
 		}
+		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
+		if(updateCheckPeriod < 0)
+		{
+			updateCheckPeriod = 0;
+		}
 		// Do we actually need to run the update check right now?
 		const time_t now = time(nullptr);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +143,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +150,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +168,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateSendGUID)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +202,104 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	if(mpt::Windows::IsWine())
+	{
+		mpt::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();
+	}
+	j["System"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) != 0)
+	{
+		j["System"]["Memory"] = memoryStatus.ullTotalPhys / 1024 / 1024;
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["tsc"] = ((GetRealProcSupport() & PROCSUPPORT_TSC) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +307,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +392,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +412,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&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;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +495,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	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_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +513,82 @@
 }
 
 
+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;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
+	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 >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		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 >= 0)
+	{
+		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_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
 
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(_T("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:"));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +596,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,19 +657,42 @@
 }
 
 
+void CUpdateSetupDlg::EnableDisableDialog()
+{
+
+	/*
+	BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
+	*/
+
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
 
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel())) : -1;
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
+	TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
 	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
@@ -452,10 +712,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10676)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,11 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +53,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +60,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +107,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +122,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +141,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10676)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -147,7 +147,7 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
update-statistics-v2.patch (34,400 bytes)   
update-statistics-v3.patch (35,294 bytes)   
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10769)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 31
+#define VER_MINORMINOR 32
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10769)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,84,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -881,7 +884,12 @@
     0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10769)
+++ mptrack/resource.h	(working copy)
@@ -974,6 +974,13 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1268,9 +1275,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        543
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2511
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10769)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,10 +331,17 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
-	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
+	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
 	, UpdateIgnoreVersion(conf, MPT_USTRING("Update"), MPT_USTRING("IgnoreVersion"), _T(""))
@@ -644,6 +651,45 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,32))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10769)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,9 +817,16 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateSendGUID;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10769)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #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
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -84,12 +86,28 @@
 
 
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +122,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			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);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +143,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +150,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +168,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateSendGUID)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +202,103 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	if(mpt::Windows::IsWine())
+	{
+		mpt::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();
+	}
+	j["System"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) != 0)
+	{
+		j["System"]["Memory"] = memoryStatus.ullTotalPhys / 1024 / 1024;
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Id"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Id"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Id"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +306,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +391,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +411,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&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;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +494,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	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_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +512,82 @@
 }
 
 
+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;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
+	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 >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		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 >= 0)
+	{
+		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_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
 
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(_T("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:"));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +595,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,19 +656,60 @@
 }
 
 
+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_STATIC_UPDATEPRIVACY)->EnableWindow(status);
+	GetDlgItem(IDC_CHECK1)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
+	GetDlgItem(IDC_EDIT_STATISTICS)->EnableWindow(status);
+
+	// disabled features
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
+	if(GetDlgItem(IDC_CHECK_UPDATEENABLED)->IsWindowEnabled() != FALSE)
+	{
+		TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	}
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
 	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
@@ -452,10 +729,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10769)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,11 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +53,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +60,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +107,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +122,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +141,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10769)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -147,7 +147,7 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
update-statistics-v3.patch (35,294 bytes)   
update-statistics-v5.patch (43,422 bytes)   
Index: common/mptOS.cpp
===================================================================
--- common/mptOS.cpp	(revision 10963)
+++ common/mptOS.cpp	(working copy)
@@ -589,6 +589,20 @@
 }
 
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host)
+{
+	std::vector<Architecture> result;
+	for(const auto & entry : hostArchitectureCanRun)
+	{
+		if(entry.Host == host)
+		{
+			result.push_back(entry.Process);
+		}
+	}
+	return result;
+}
+
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
@@ -629,6 +643,18 @@
 
 #endif // MPT_OS_WINDOWS
 
+uint64 GetSystemMemorySize()
+{
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) == 0)
+	{
+		return 0;
+	}
+	return memoryStatus.ullTotalPhys;
+}
+
 static bool SystemIsWine(bool allowDetection = true)
 {
 	#if MPT_OS_WINDOWS
Index: common/mptOS.h
===================================================================
--- common/mptOS.h	(revision 10963)
+++ common/mptOS.h	(working copy)
@@ -183,11 +183,15 @@
 
 EmulationLevel HostCanRun(Architecture host, Architecture process) noexcept;
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host);
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
 #if defined(MODPLUG_TRACKER)
 
+uint64 GetSystemMemorySize();
+
 void PreventWineDetection();
 
 bool IsOriginal();
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10963)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 37
+#define VER_MINORMINOR 38
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 10963)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -1071,8 +1071,25 @@
 		font.size = Clamp(Util::GetDPIy(m_pMainWnd->m_hWnd) / 96 - 1, 0, 9);
 		TrackerSettings::Instance().patternFont = font;
 		new WelcomeDlg(m_pMainWnd);
+
+		TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+
 	} else
 	{
+
+		// ask if user wants to contribute system statistics
+		if(!TrackerSettings::Instance().UpdateStatisticsConsentAsked)
+		{
+			TrackerSettings::Instance().UpdateStatistics = (ConfirmAnswer::cnfYes == Reporting::Confirm(
+				MPT_USTRING("Do you want to contribute to OpenMPT by providing system statistics?\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING("\r\n")) + MPT_USTRING("\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::format(MPT_USTRING("This option was previously %1 on your system.\r\n"))(TrackerSettings::Instance().UpdateStatistics ? MPT_USTRING("enabled") : MPT_USTRING("disabled")),
+				false, !TrackerSettings::Instance().UpdateStatistics.Get()));
+			TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+		}
+
 		// Update check
 		CUpdateCheck::DoAutoUpdateCheck();
 
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10963)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,84,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -374,23 +377,26 @@
     PUSHBUTTON      "&Cancel",IDCANCEL,138,24,50,14
 END
 
-IDD_WECLOME DIALOGEX 0, 0, 256, 137
+IDD_WECLOME DIALOGEX 0, 0, 257, 261
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Welcome to OpenMPT!"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "&OK",IDOK,198,116,50,14
+    DEFPUSHBUTTON   "&OK",IDOK,198,236,50,14
     LTEXT           "Please review the following settings before using this software:",IDC_STATIC,6,6,246,8
     CONTROL         "&Automatically check for new versions of OpenMPT",IDC_CHECK1,
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,246,10
+    CONTROL         "Help OpenMPT development by providing basic s&tatistics",IDC_CHECK3,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,42,234,10
+    LTEXT           "Static",IDC_STATIC_WELCOME_STATISTICS,30,54,216,102
     CONTROL         "&Use a big font in the pattern editor",IDC_CHECK2,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,42,246,10
-    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,63,108,8
-    COMBOBOX        IDC_COMBO1,114,60,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,82,172,8
-    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,79,50,14
-    EDITTEXT        IDC_EDIT1,6,95,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
-    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,116,60,14
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,162,246,10
+    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,183,108,8
+    COMBOBOX        IDC_COMBO1,114,180,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,202,172,8
+    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,199,50,14
+    EDITTEXT        IDC_EDIT1,6,215,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,236,60,14
 END
 
 IDD_UPDATE DIALOGEX 0, 0, 196, 138
@@ -751,9 +757,9 @@
     IDD_WECLOME, DIALOG
     BEGIN
         LEFTMARGIN, 7
-        RIGHTMARGIN, 249
+        RIGHTMARGIN, 250
         TOPMARGIN, 7
-        BOTTOMMARGIN, 130
+        BOTTOMMARGIN, 254
     END
 
     IDD_UPDATE, DIALOG
@@ -931,7 +937,17 @@
     0, 100, 100, 0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+IDD_WECLOME AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
+
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10963)
+++ mptrack/resource.h	(working copy)
@@ -975,6 +975,14 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
+#define IDC_STATIC_WELCOME_STATISTICS   2511
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1269,9 +1277,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        544
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2512
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10963)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,10 +331,19 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
-	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
+	, UpdateStatisticsConsentAsked(conf, MPT_USTRING("Update"), MPT_USTRING("StatistisConsentAsked"), false)
+	, UpdateStatistics(conf, MPT_USTRING("Update"), MPT_USTRING("Statistis"), false)
+	, UpdateSendGUID_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
 	, UpdateIgnoreVersion(conf, MPT_USTRING("Update"), MPT_USTRING("IgnoreVersion"), _T(""))
@@ -644,6 +653,47 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,38))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+		conf.Forget(UpdateSendGUID_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10963)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,10 +817,19 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
-	Setting<bool> UpdateSendGUID;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
+	Setting<bool> UpdateStatisticsConsentAsked;
+	Setting<bool> UpdateStatistics;
+	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
 	Setting<CString> UpdateIgnoreVersion;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10963)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #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
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -83,13 +85,46 @@
 
 
 
+mpt::ustring CUpdateCheck::GetStatisticsUserInformation(bool shortText)
+{
+	if(shortText)
+	{
+		return MPT_USTRING("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:");
+	} else
+	{
+		return MPT_USTRING("")
+			+ MPT_USTRING("When checking for updates, OpenMPT can additionally collect some basic statistical information.") + MPT_USTRING("\n")
+			+ MPT_USTRING("A randomized user ID is created and transmitted alongside the update check. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer.") + MPT_USTRING("\n")
+			+ MPT_USTRING("OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems.") + MPT_USTRING("\n")
+			+ MPT_USTRING("Without this statistical information, the OpenMPT developers would be blind with respect to what systems are used to run OpenMPT. This makes deciding where to focus development plain guesswork.") + MPT_USTRING("\n")
+			+ MPT_USTRING("OpenMPT collects the following statistical data points: OpenMPT version, Windows version, type of CPU, amount of RAM, configured update check frequency of OpenMPT.")
+			;
+	}
+}
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +139,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			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);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +160,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +167,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +185,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +219,103 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	std::vector<mpt::Windows::Architecture> architectures = mpt::Windows::GetSupportedProcessArchitectures(mpt::Windows::GetHostArchitecture());
+	for(const auto & arch : architectures)
+	{
+		j["System"]["Windows"]["ProcessArchitectures"][mpt::ToCharset(mpt::CharsetUTF8, mpt::Windows::Name(arch))] = true;
+	}
+	j["System"]["Memory"] = mpt::Windows::GetSystemMemorySize() / 1024 / 1024;  // MB
+	if(mpt::Windows::IsWine())
+	{
+		mpt::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();
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Id"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Id"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Id"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["lm"] = ((GetRealProcSupport() & PROCSUPPORT_LM) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +323,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +408,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +428,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&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;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +511,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	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_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +529,82 @@
 }
 
 
+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;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
-	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
+	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 >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		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 >= 0)
+	{
+		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_CHECK1, TrackerSettings::Instance().UpdateStatistics ? BST_CHECKED : BST_UNCHECKED);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(mpt::ToCString(CUpdateCheck::GetStatisticsUserInformation(true)));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +612,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,20 +673,61 @@
 }
 
 
+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_STATIC_UPDATEPRIVACY)->EnableWindow(status);
+	GetDlgItem(IDC_CHECK1)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
+	GetDlgItem(IDC_EDIT_STATISTICS)->EnableWindow(status);
+
+	// disabled features
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
-	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+	if(GetDlgItem(IDC_CHECK_UPDATEENABLED)->IsWindowEnabled() != FALSE)
+	{
+		TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	}
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
 }
@@ -452,10 +746,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10963)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,13 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetStatisticsUserInformation(bool shortText);
+
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +55,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +62,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +109,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +124,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +143,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10963)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -119,6 +119,8 @@
 	combo->SetItemDataPtr(combo->AddString(_T("FastTracker 2")), (void*)("US_mpt-ft2_classic"));
 
 	CheckDlgButton(IDC_CHECK1, BST_CHECKED);
+	CheckDlgButton(IDC_CHECK3, BST_UNCHECKED);
+	GetDlgItem(IDC_STATIC_WELCOME_STATISTICS)->SetWindowText(mpt::ToCString(mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING(" "))));
 	CheckDlgButton(IDC_CHECK2, (TrackerSettings::Instance().patternFont.Get().name == PATTERNFONT_LARGE) ? BST_CHECKED : BST_UNCHECKED);
 
 	ShowWindow(SW_SHOW);
@@ -147,7 +149,8 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK3) != BST_UNCHECKED);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
update-statistics-v5.patch (43,422 bytes)   
update-statistics-v7.patch (43,972 bytes)   
Index: common/mptOS.cpp
===================================================================
--- common/mptOS.cpp	(revision 10965)
+++ common/mptOS.cpp	(working copy)
@@ -589,6 +589,20 @@
 }
 
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host)
+{
+	std::vector<Architecture> result;
+	for(const auto & entry : hostArchitectureCanRun)
+	{
+		if(entry.Host == host)
+		{
+			result.push_back(entry.Process);
+		}
+	}
+	return result;
+}
+
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
@@ -629,6 +643,18 @@
 
 #endif // MPT_OS_WINDOWS
 
+uint64 GetSystemMemorySize()
+{
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) == 0)
+	{
+		return 0;
+	}
+	return memoryStatus.ullTotalPhys;
+}
+
 static bool SystemIsWine(bool allowDetection = true)
 {
 	#if MPT_OS_WINDOWS
Index: common/mptOS.h
===================================================================
--- common/mptOS.h	(revision 10965)
+++ common/mptOS.h	(working copy)
@@ -183,11 +183,15 @@
 
 EmulationLevel HostCanRun(Architecture host, Architecture process) noexcept;
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host);
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
 #if defined(MODPLUG_TRACKER)
 
+uint64 GetSystemMemorySize();
+
 void PreventWineDetection();
 
 bool IsOriginal();
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10965)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 38
+#define VER_MINORMINOR 39
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 10965)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -1071,8 +1071,25 @@
 		font.size = Clamp(Util::GetDPIy(m_pMainWnd->m_hWnd) / 96 - 1, 0, 9);
 		TrackerSettings::Instance().patternFont = font;
 		new WelcomeDlg(m_pMainWnd);
+
+		TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+
 	} else
 	{
+
+		// ask if user wants to contribute system statistics
+		if(!TrackerSettings::Instance().UpdateStatisticsConsentAsked)
+		{
+			TrackerSettings::Instance().UpdateStatistics = (ConfirmAnswer::cnfYes == Reporting::Confirm(
+				MPT_USTRING("Do you want to contribute to OpenMPT by providing system statistics?\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING("\r\n")) + MPT_USTRING("\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::format(MPT_USTRING("This option was previously %1 on your system.\r\n"))(TrackerSettings::Instance().UpdateStatistics ? MPT_USTRING("enabled") : MPT_USTRING("disabled")),
+				false, !TrackerSettings::Instance().UpdateStatistics.Get()));
+			TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+		}
+
 		// Update check
 		CUpdateCheck::DoAutoUpdateCheck();
 
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10965)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,84,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -374,23 +377,26 @@
     PUSHBUTTON      "&Cancel",IDCANCEL,138,24,50,14
 END
 
-IDD_WECLOME DIALOGEX 0, 0, 256, 137
+IDD_WECLOME DIALOGEX 0, 0, 257, 261
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Welcome to OpenMPT!"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "&OK",IDOK,198,116,50,14
+    DEFPUSHBUTTON   "&OK",IDOK,198,236,50,14
     LTEXT           "Please review the following settings before using this software:",IDC_STATIC,6,6,246,8
     CONTROL         "&Automatically check for new versions of OpenMPT",IDC_CHECK1,
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,246,10
+    CONTROL         "Help OpenMPT development by providing basic s&tatistics",IDC_CHECK3,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,42,234,10
+    LTEXT           "Static",IDC_STATIC_WELCOME_STATISTICS,30,54,216,102
     CONTROL         "&Use a big font in the pattern editor",IDC_CHECK2,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,42,246,10
-    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,63,108,8
-    COMBOBOX        IDC_COMBO1,114,60,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,82,172,8
-    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,79,50,14
-    EDITTEXT        IDC_EDIT1,6,95,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
-    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,116,60,14
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,162,246,10
+    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,183,108,8
+    COMBOBOX        IDC_COMBO1,114,180,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,202,172,8
+    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,199,50,14
+    EDITTEXT        IDC_EDIT1,6,215,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,236,60,14
 END
 
 IDD_UPDATE DIALOGEX 0, 0, 196, 138
@@ -751,9 +757,9 @@
     IDD_WECLOME, DIALOG
     BEGIN
         LEFTMARGIN, 7
-        RIGHTMARGIN, 249
+        RIGHTMARGIN, 250
         TOPMARGIN, 7
-        BOTTOMMARGIN, 130
+        BOTTOMMARGIN, 254
     END
 
     IDD_UPDATE, DIALOG
@@ -931,7 +937,17 @@
     0, 100, 100, 0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+IDD_WECLOME AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
+
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10965)
+++ mptrack/resource.h	(working copy)
@@ -975,6 +975,14 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
+#define IDC_STATIC_WELCOME_STATISTICS   2511
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1269,9 +1277,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        544
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2512
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10965)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -332,10 +332,19 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
-	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
+	, UpdateStatisticsConsentAsked(conf, MPT_USTRING("Update"), MPT_USTRING("StatistisConsentAsked"), false)
+	, UpdateStatistics(conf, MPT_USTRING("Update"), MPT_USTRING("Statistis"), false)
+	, UpdateSendGUID_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
 	, UpdateIgnoreVersion(conf, MPT_USTRING("Update"), MPT_USTRING("IgnoreVersion"), _T(""))
@@ -645,6 +654,47 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,39))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+		conf.Forget(UpdateSendGUID_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10965)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -818,10 +818,19 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
-	Setting<bool> UpdateSendGUID;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
+	Setting<bool> UpdateStatisticsConsentAsked;
+	Setting<bool> UpdateStatistics;
+	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
 	Setting<CString> UpdateIgnoreVersion;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10965)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #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
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -83,13 +85,46 @@
 
 
 
+mpt::ustring CUpdateCheck::GetStatisticsUserInformation(bool shortText)
+{
+	if(shortText)
+	{
+		return MPT_USTRING("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:");
+	} else
+	{
+		return MPT_USTRING("")
+			+ MPT_USTRING("When checking for updates, OpenMPT can additionally collect some basic statistical information.") + MPT_USTRING(" ")
+			+ MPT_USTRING("A randomized user ID is created and transmitted alongside the update check. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer.") + MPT_USTRING(" ")
+			+ MPT_USTRING("OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems.") + MPT_USTRING(" ")
+			+ MPT_USTRING("Without this statistical information, the OpenMPT developers would be blind with respect to what systems are used to run OpenMPT. This makes deciding where to focus development plain guesswork.") + MPT_USTRING("\n")
+			+ MPT_USTRING("OpenMPT would collect the following statistical data points: OpenMPT version, Windows version, type of CPU, amount of RAM, configured update check frequency of OpenMPT.")
+			;
+	}
+}
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,16 +139,25 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			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);
-		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
+		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).
+		if(difftime(now, lastCheck) > 0.0)
 		{
-			return;
+			if(difftime(now, lastCheck) < static_cast<double>(updateCheckPeriod * 86400))
+			{
+				return;
+			}
 		}
 
 		// Never ran update checks before, so we notify the user of automatic update checks.
@@ -121,7 +165,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +172,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +190,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +224,103 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	std::vector<mpt::Windows::Architecture> architectures = mpt::Windows::GetSupportedProcessArchitectures(mpt::Windows::GetHostArchitecture());
+	for(const auto & arch : architectures)
+	{
+		j["System"]["Windows"]["ProcessArchitectures"][mpt::ToCharset(mpt::CharsetUTF8, mpt::Windows::Name(arch))] = true;
+	}
+	j["System"]["Memory"] = mpt::Windows::GetSystemMemorySize() / 1024 / 1024;  // MB
+	if(mpt::Windows::IsWine())
+	{
+		mpt::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();
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Id"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Id"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Id"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["lm"] = ((GetRealProcSupport() & PROCSUPPORT_LM) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +328,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +413,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +433,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&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;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +516,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	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_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +534,82 @@
 }
 
 
+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;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
-	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
+	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 >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		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 >= 0)
+	{
+		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_CHECK1, TrackerSettings::Instance().UpdateStatistics ? BST_CHECKED : BST_UNCHECKED);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(mpt::ToCString(CUpdateCheck::GetStatisticsUserInformation(true)));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +617,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,20 +678,61 @@
 }
 
 
+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_STATIC_UPDATEPRIVACY)->EnableWindow(status);
+	GetDlgItem(IDC_CHECK1)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
+	GetDlgItem(IDC_EDIT_STATISTICS)->EnableWindow(status);
+
+	// disabled features
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(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()));
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
-	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+	if(GetDlgItem(IDC_CHECK_UPDATEENABLED)->IsWindowEnabled() != FALSE)
+	{
+		TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	}
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
 }
@@ -452,10 +751,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10965)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,13 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetStatisticsUserInformation(bool shortText);
+
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +55,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +62,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +109,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +124,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,6 +143,7 @@
 	CUpdateSetupDlg();
 
 protected:
+	void DoDataExchange(CDataExchange *pDX) override;
 	BOOL OnInitDialog() override;
 	void OnOK() override;
 	BOOL OnSetActive() override;
@@ -117,13 +150,15 @@
 
 	void SettingChanged(const SettingPath &changedPath) override;
 
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10965)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -119,6 +119,8 @@
 	combo->SetItemDataPtr(combo->AddString(_T("FastTracker 2")), (void*)("US_mpt-ft2_classic"));
 
 	CheckDlgButton(IDC_CHECK1, BST_CHECKED);
+	CheckDlgButton(IDC_CHECK3, BST_UNCHECKED);
+	GetDlgItem(IDC_STATIC_WELCOME_STATISTICS)->SetWindowText(mpt::ToCString(mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING(" "))));
 	CheckDlgButton(IDC_CHECK2, (TrackerSettings::Instance().patternFont.Get().name == PATTERNFONT_LARGE) ? BST_CHECKED : BST_UNCHECKED);
 
 	ShowWindow(SW_SHOW);
@@ -147,7 +149,8 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK3) != BST_UNCHECKED);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
update-statistics-v7.patch (43,972 bytes)   
Has the bug occurred in previous versions?
Tested code revision (in case you know it)

Relationships

related to 0000649 resolvedmanx modern file type registration (was: Re-register MPT instantly
related to 0001428 resolvedSaga Musix Avoid modal update notifcation dialog 
related to 0001466 resolvedmanx OpenMPT fails to re-open previously opened files when restarted via RestartManager 
related to 0001532 resolvedmanx Remove legacy update code 
related to 0001123 resolvedmanx Provide unified multi-arch installer 
related to 0001185 resolvedmanx Restructure MSVC bin directory layout 
related to 0001186 resolvedmanx Remove Windows XP support 
related to 0001213 resolvedmanx Admin-level installer modifies per-user directories 
related to 0001214 resolvedmanx Remove portable mode from installer 
related to 0001377 resolvedSaga Musix Making OpenMPT signed software 

Activities

manx

manx

2017-08-14 11:27

administrator   ~0003167

I'd prefer to use a new update URL. That way, the client can also send JSON data, which could easily be extended without breaking backwards compatibility in the future (which is a major hassle with the current way of doing things).

Other than that, I really like the proposal to send all build variants. That's exactly what I also had in mind.

Additionally, we should at least take a look at WinSparkle ( https://winsparkle.org/ ). I do not know if the sparkle specification would be enough for what OpenMPT needs, but even if it is not, and even if we do not use RSS/XML (like Sparkle), there might be good ideas in there to copy or mimic.

manx

manx

2017-09-22 15:10

administrator   ~0003230

Even though downloading the automated updater from a TLS host with a valid certificate is technically and in practice enough to ensure authenticity, we might want to also look into additionally signing the installer itself. Either with a proper code signing certificate, or with a custom pinned key.

Saga Musix

Saga Musix

2017-09-22 15:28

administrator   ~0003232

I have looked into code signing certificates several times in the past, and there used to be free certificates for open-source developers from Certum, but those are no longer free. While they are relatively cheap, they still require documents for verification that I would be hesistant to provide (see https://www.certum.eu/certum/cert,offer_en_open_source_cs.xml)

manx

manx

2017-09-22 15:38

administrator   ~0003233

Which is why I suggested as an alternative just additionally signing the updater with custom key even without an official certificate which would still guard against any attacks on the domain. In particular, especially for signing the updater there is actually no semantic requirement to rely on any common certificate authority infrastructure.
Signing the OpenMPT executable itself would indeed require an official code signing certificate, however that aspect is totally orthogonal to automatic updates.

manx

manx

2018-08-22 10:55

administrator   ~0003605

Attached a quick mockup of a suggested update settings dialog.

Changes:

  • Update frequency moved to a combo box as this is of less importance and should not consume as much screen real estate
  • Update URL is completely gone (can be retained as a hidden setting of course), and replaced by an update channel mechanism as is done for most other software. This additionally add a stable-next channel which would be HEAD of the current release branch.
  • Explicitly show what information will be sent to the server for statistics purposes
  • Add combo box to switch between notify/download/install

All but the last change can already be implemented without actually changing the update/installer mechanism itself.

manx

manx

2018-11-09 09:12

administrator   ~0003708

  • [Mod] Update Check: Rework update dialog. The explicit setting of the update URL is moved to hidden settings and instead a selection of the update channel is provided (i.e. -release or -development), which should be way less confusing to users.
  • [Fix] Update Check: Make sure that each user and/or installation is at least once asked prominently if they want to contribute statistics to OpenMPT. Change the default to not providing statistics for privacy reasons.
  • [Imp] Update Check: Make completely transparent what information is sent to OpenMPT as part of the update check and statistics data.
  • [Mod] Update Check: Add possibility to check for updates at each program startup.
  • [Imp] Update Check: In case the last update check supposedly happened in the future, we now always check for updates instead of comparing against the configured interval. Future dates had probably been caused by a wrong system clock, in which case we cannot depend on its value (neither before nor after) in order to determine a real world time interval. We always check in this case for security reasons in order to get updates to the users.
  • [New] Update Check: Add client side of new statistics gathering API. This uses a JSON REST interface. The old API is still supported and used, which enables us to convert the server part at a later time. We should implement some JSON/REST thingy just now in order to test it and make it work reliably for when we convert the update mechanism itself to JSON.
  • [New] Statistics: Add CPU type and flags, system memory, precise OS version, and supported process architectures to gathered statistics data.
manx

manx

2018-11-11 15:49

administrator   ~0003712

Patch committed as r10970.

manx

manx

2020-06-07 07:32

administrator   ~0004373

Since r12991 / 1.30.00.04, the new multi-arch installer supports silent auto-updates via OpenMPT-1.30.00.04-Stup.exe /SP-1 /SILENT in all installation modes.

manx

manx

2020-07-27 07:59

administrator   ~0004408

autoupdate-v10.patch (75,630 bytes)   
Index: common/BuildSettings.h
===================================================================
--- common/BuildSettings.h	(revision 13367)
+++ common/BuildSettings.h	(working copy)
@@ -194,6 +194,8 @@
 
 #if defined(MODPLUG_TRACKER)
 
+#define MPT_UPDATE_LEGACY 1
+
 // Enable built-in test suite.
 #if defined(MPT_BUILD_DEBUG) || defined(MPT_BUILD_CHECKED)
 #define ENABLE_TESTS
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 13367)
+++ common/versionNumber.h	(working copy)
@@ -18,6 +18,6 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      30
 #define VER_MINOR      00
-#define VER_MINORMINOR 07
+#define VER_MINORMINOR 08
 
 OPENMPT_NAMESPACE_END
Index: installer/generate_update_json.py
===================================================================
--- installer/generate_update_json.py	(revision 13367)
+++ installer/generate_update_json.py	(working copy)
@@ -161,7 +161,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-Setup.update.json",
 				"type": "installer",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":6, "version_minor":1, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": { "x86":True },
@@ -172,7 +172,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-portable-x86.update.json",
 				"type": "archive",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":10, "version_minor":0, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": {},
@@ -183,7 +183,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-portable-x86-legacy.update.json",
 				"type": "archive",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":6, "version_minor":1, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": {},
@@ -194,7 +194,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-portable-amd64.update.json",
 				"type": "archive",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":10, "version_minor":0, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": {},
@@ -205,7 +205,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-portable-amd64-legacy.update.json",
 				"type": "archive",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":6, "version_minor":1, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": {},
@@ -216,7 +216,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-portable-arm.update.json",
 				"type": "archive",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":10, "version_minor":0, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": {},
@@ -227,7 +227,7 @@
 				"url": "https://builds.openmpt.org/builds/auto/openmpt/pkg.win/" + OPENMPT_VERSION_MAJORMAJOR + "." + OPENMPT_VERSION_MAJOR + "/OpenMPT-" + version + "-portable-arm64.update.json",
 				"type": "archive",
 				"can_autoupdate": True,
-				"autoupdate_minversion": "1.30.00.04",
+				"autoupdate_minversion": "1.30.00.08",
 				"os": "windows",
 				"required_windows_version": { "version_major":10, "version_minor":0, "servicepack_major":0, "servicepack_minor":0, "build":0, "wine_major":1, "wine_minor":8, "wine_update":0 },
 				"required_architectures": {},
Index: mptrack/MainFrm.cpp
===================================================================
--- mptrack/MainFrm.cpp	(revision 13367)
+++ mptrack/MainFrm.cpp	(working copy)
@@ -46,6 +46,7 @@
 #include "../soundlib/plugins/PluginManager.h"
 #include "Vstplug.h"
 #include "FileDialog.h"
+#include "ProgressDialog.h"
 #include <HtmlHelp.h>
 
 
@@ -109,9 +110,11 @@
 	ON_MESSAGE(WM_MOD_SETMODIFIED,			&CMainFrame::OnSetModified)
 	ON_COMMAND(ID_INTERNETUPDATE,			&CMainFrame::OnInternetUpdate)
 	ON_COMMAND(ID_HELP_SHOWSETTINGSFOLDER,	&CMainFrame::OnShowSettingsFolder)
+	ON_MESSAGE(MPT_WM_APP_UPDATECHECK_START, &CMainFrame::OnUpdateCheckStart)
 	ON_MESSAGE(MPT_WM_APP_UPDATECHECK_PROGRESS, &CMainFrame::OnUpdateCheckProgress)
+	ON_MESSAGE(MPT_WM_APP_UPDATECHECK_CANCELED, &CMainFrame::OnUpdateCheckCanceled)
+	ON_MESSAGE(MPT_WM_APP_UPDATECHECK_FAILURE, &CMainFrame::OnUpdateCheckFailure)
 	ON_MESSAGE(MPT_WM_APP_UPDATECHECK_SUCCESS, &CMainFrame::OnUpdateCheckSuccess)
-	ON_MESSAGE(MPT_WM_APP_UPDATECHECK_FAILURE, &CMainFrame::OnUpdateCheckFailure)
 	ON_COMMAND(ID_HELPSHOW,					&CMainFrame::OnHelp)
 
 	ON_COMMAND_RANGE(ID_MRU_LIST_FIRST, ID_MRU_LIST_LAST, &CMainFrame::OnOpenMRUItem)
@@ -1904,8 +1907,10 @@
 	if(mpt::Windows::IsWine()) dlg.AddPage(&winedlg);
 	m_bOptionsLocked = true;
 	m_SoundCardOptionsDialog = &sounddlg;
+	m_UpdateOptionsDialog = &updatedlg;
 	dlg.DoModal();
 	m_SoundCardOptionsDialog = nullptr;
+	m_UpdateOptionsDialog = nullptr;
 	m_bOptionsLocked = false;
 	m_wndTree.OnOptionsChanged();
 }
@@ -2552,18 +2557,110 @@
 }
 
 
+
+class CUpdateCheckProgressDialog
+	: public CProgressDialog
+{
+public:
+	CUpdateCheckProgressDialog(CWnd *parent)
+		: CProgressDialog(parent)
+	{
+		return;
+	}
+	void Run() override
+	{
+	}
+};
+
+static std::unique_ptr<CUpdateCheckProgressDialog> g_UpdateCheckProgressDialog = nullptr;
+
+
+
+LRESULT CMainFrame::OnUpdateCheckStart(WPARAM wparam, LPARAM lparam)
+{
+	bool isAutoUpdate = CUpdateCheck::IsAutoUpdateFromMessage(wparam, lparam);
+	CString updateText = _T("Checking for updates...");
+	if(isAutoUpdate)
+	{
+		SetHelpText(updateText);
+	} else if(m_UpdateOptionsDialog)
+	{
+		m_UpdateOptionsDialog->SetDlgItemText(IDC_LASTUPDATE, updateText);
+	} else
+	{
+		if(!g_UpdateCheckProgressDialog)
+		{
+			g_UpdateCheckProgressDialog = std::make_unique<CUpdateCheckProgressDialog>(CMainFrame::GetMainFrame());
+			g_UpdateCheckProgressDialog->Create(IDD_PROGRESS, CMainFrame::GetMainFrame());
+			g_UpdateCheckProgressDialog->SetTitle(_T("Checking for updates..."));
+			g_UpdateCheckProgressDialog->SetText(_T("Checking for updates..."));
+			g_UpdateCheckProgressDialog->SetAbortText(_T("&Cancel"));
+			g_UpdateCheckProgressDialog->SetRange(0, 100);
+			g_UpdateCheckProgressDialog->ShowWindow(SW_SHOWDEFAULT);
+		}
+		if(g_UpdateCheckProgressDialog)
+		{
+			g_UpdateCheckProgressDialog->SetProgress(0);
+		}
+	}
+	return TRUE;
+}
+
+
 LRESULT CMainFrame::OnUpdateCheckProgress(WPARAM wparam, LPARAM lparam)
 {
-	MPT_UNREFERENCED_PARAMETER(wparam);
-	MPT_UNREFERENCED_PARAMETER(lparam);
+	bool isAutoUpdate = wparam ? true : false;
+	CString updateText = MPT_CFORMAT("Checking for updates... {}%")(lparam);
+	if(isAutoUpdate)
+	{
+		SetHelpText(updateText);
+	} else if(m_UpdateOptionsDialog)
+	{
+		m_UpdateOptionsDialog->SetDlgItemText(IDC_LASTUPDATE, updateText);
+	} else
+	{
+		if(g_UpdateCheckProgressDialog)
+		{
+			g_UpdateCheckProgressDialog->SetProgress(lparam);
+			if(g_UpdateCheckProgressDialog->m_abort)
+			{
+				return FALSE;
+			}
+		}
+	}
 	return TRUE;
 }
 
 
-LRESULT CMainFrame::OnUpdateCheckSuccess(WPARAM wparam, LPARAM lparam)
+LRESULT CMainFrame::OnUpdateCheckCanceled(WPARAM wparam, LPARAM lparam)
 {
-	TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(CUpdateCheck::ResultFromMessage(wparam, lparam).CheckTime);
-	CUpdateCheck::ShowSuccessGUI(wparam, lparam);
+	bool isAutoUpdate = CUpdateCheck::IsAutoUpdateFromMessage(wparam, lparam);
+	CString updateText = _T("Checking for updates... Canceled.");
+	if(isAutoUpdate)
+	{
+		SetHelpText(updateText);
+	} else if(m_UpdateOptionsDialog)
+	{
+		m_UpdateOptionsDialog->SetDlgItemText(IDC_LASTUPDATE, updateText);
+		m_UpdateOptionsDialog->SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
+	} else
+	{
+		if(g_UpdateCheckProgressDialog)
+		{
+			g_UpdateCheckProgressDialog->DestroyWindow();
+			g_UpdateCheckProgressDialog = nullptr;
+		}
+	}
+	if(isAutoUpdate)
+	{
+		SetHelpText(_T(""));
+	} else if(m_UpdateOptionsDialog)
+	{
+		// nothing
+	} else
+	{
+		// nothing
+	}
 	return TRUE;
 }
 
@@ -2570,11 +2667,81 @@
 
 LRESULT CMainFrame::OnUpdateCheckFailure(WPARAM wparam, LPARAM lparam)
 {
+	bool isAutoUpdate = CUpdateCheck::IsAutoUpdateFromMessage(wparam, lparam);
+	CString updateText = MPT_CFORMAT("Checking for updates failed: {}")(CUpdateCheck::GetFailureMessage(wparam, lparam)).GetString();
+	if(isAutoUpdate)
+	{
+		SetHelpText(updateText);
+	} else if(m_UpdateOptionsDialog)
+	{
+		m_UpdateOptionsDialog->SetDlgItemText(IDC_LASTUPDATE, updateText);
+		m_UpdateOptionsDialog->SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
+	} else
+	{
+		if(g_UpdateCheckProgressDialog)
+		{
+			g_UpdateCheckProgressDialog->DestroyWindow();
+			g_UpdateCheckProgressDialog = nullptr;
+		}
+	}
 	CUpdateCheck::ShowFailureGUI(wparam, lparam);
+	if(isAutoUpdate)
+	{
+		SetHelpText(_T(""));
+	} else if(m_UpdateOptionsDialog)
+	{
+		// nothing
+	} else
+	{
+		// nothing
+	}
 	return TRUE;
 }
 
 
+LRESULT CMainFrame::OnUpdateCheckSuccess(WPARAM wparam, LPARAM lparam)
+{
+	bool isAutoUpdate = CUpdateCheck::IsAutoUpdateFromMessage(wparam, lparam);
+	CString updateText = MPT_CFORMAT("Checking for updates... Done.")().GetString();
+	// TODO:
+	// UpdateToolbarUpdateIndicator(CUpdateCheck::ResultFromMessage(wparam, lparam));
+	if(isAutoUpdate)
+	{
+		SetHelpText(updateText);
+	} else if(m_UpdateOptionsDialog)
+	{
+		m_UpdateOptionsDialog->SetDlgItemText(IDC_LASTUPDATE, updateText);
+	} else
+	{
+		SetHelpText(updateText);
+		if(g_UpdateCheckProgressDialog)
+		{
+			g_UpdateCheckProgressDialog->DestroyWindow();
+			g_UpdateCheckProgressDialog = nullptr;
+		}
+	}
+	CUpdateCheck::ShowSuccessGUI(wparam, lparam);
+	if(isAutoUpdate)
+	{
+		SetHelpText(_T(""));
+	} else if(m_UpdateOptionsDialog)
+	{
+		// nothing
+	} else
+	{
+		// nothing
+	}
+	return TRUE;
+}
+
+
+void CMainFrame::OnToolbarUpdateIndicatorClick()
+{
+	// TODO
+	CUpdateCheck::DoManualUpdateCheck();
+}
+
+
 void CMainFrame::OnHelp()
 {
 	CView *view = GetActiveView();
Index: mptrack/Mainfrm.h
===================================================================
--- mptrack/Mainfrm.h	(revision 13367)
+++ mptrack/Mainfrm.h	(working copy)
@@ -60,9 +60,11 @@
 
 enum
 {
-	MPT_WM_APP_UPDATECHECK_PROGRESS = WM_APP + 1,
-	MPT_WM_APP_UPDATECHECK_SUCCESS = WM_APP + 2,
-	MPT_WM_APP_UPDATECHECK_FAILURE = WM_APP + 3,
+	MPT_WM_APP_UPDATECHECK_START = WM_APP + 1,
+	MPT_WM_APP_UPDATECHECK_PROGRESS = WM_APP + 2,
+	MPT_WM_APP_UPDATECHECK_CANCELED = WM_APP + 3,
+	MPT_WM_APP_UPDATECHECK_FAILURE = WM_APP + 4,
+	MPT_WM_APP_UPDATECHECK_SUCCESS = WM_APP + 5,
 };
 
 enum
@@ -303,6 +305,7 @@
 	UINT m_nAvgMixChn = 0, m_nMixChn = 0;
 	// Misc
 	class COptionsSoundcard *m_SoundCardOptionsDialog = nullptr;
+	class CUpdateSetupDlg *m_UpdateOptionsDialog = nullptr;
 	DWORD helpCookie = 0;
 	bool m_bOptionsLocked = false;
 
@@ -529,9 +532,12 @@
 	afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
 	afx_msg void OnInternetUpdate();
 	afx_msg void OnShowSettingsFolder();
+	afx_msg LRESULT OnUpdateCheckStart(WPARAM wparam, LPARAM lparam);
 	afx_msg LRESULT OnUpdateCheckProgress(WPARAM wparam, LPARAM lparam);
+	afx_msg LRESULT OnUpdateCheckCanceled(WPARAM wparam, LPARAM lparam);
+	afx_msg LRESULT OnUpdateCheckFailure(WPARAM wparam, LPARAM lparam);
 	afx_msg LRESULT OnUpdateCheckSuccess(WPARAM wparam, LPARAM lparam);
-	afx_msg LRESULT OnUpdateCheckFailure(WPARAM wparam, LPARAM lparam);
+	afx_msg void OnToolbarUpdateIndicatorClick();
 	afx_msg void OnHelp();
 	afx_msg void OnDropFiles(HDROP hDropInfo);
 	afx_msg BOOL OnQueryEndSession();
Index: mptrack/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 13367)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -1069,9 +1069,6 @@
 		new WelcomeDlg(m_pMainWnd);
 	} else
 	{
-
-		CUpdateCheck::DoAutoUpdateCheck();
-
 		bool deprecatedSoundDevice = GetSoundDevicesManager()->FindDeviceInfo(TrackerSettings::Instance().GetSoundDeviceIdentifier()).IsDeprecated();
 		bool showSettings = deprecatedSoundDevice && !TrackerSettings::Instance().m_SoundDeprecatedDeviceWarningShown && (Reporting::Confirm(
 			U_("You have currently selected a sound device which is deprecated. MME/WaveOut and DirectSound support will be removed in a future OpenMPT version.\n") +
@@ -1098,6 +1095,14 @@
 		pMainFrame->PlayPreview();
 	}
 
+	if(!TrackerSettings::Instance().FirstRun)
+	{
+		if(CUpdateCheck::IsSuitableUpdateMoment())
+		{
+			CUpdateCheck::DoAutoUpdateCheck();
+		}
+	}
+
 	return TRUE;
 }
 
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 13367)
+++ mptrack/mptrack.rc	(working copy)
@@ -246,24 +246,26 @@
 BEGIN
     CONTROL         "Enable Online &Update Check",IDC_CHECK_UPDATEENABLED,
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,106,10
-    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,24,276,48
-    LTEXT           "Automatically check on program &start:",IDC_STATIC_UPDATEFREQUENCY,12,36,126,12,SS_CENTERIMAGE
-    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,36,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,36,60,12
-    LTEXT           "",IDC_LASTUPDATE,12,55,264,12
-    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,78,276,84
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,24,276,66
+    CONTROL         "&Install Updates automatically",IDC_CHECK_UPDATEINSTALLAUTOMATICALLY,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,36,264,12
+    LTEXT           "Automatically check on program &start:",IDC_STATIC_UPDATEFREQUENCY,12,54,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,54,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,54,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,73,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,96,276,84
     CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,264,12
-    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,102,264,36
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,108,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,120,264,36
     CONTROL         "<a>Show which information would be transmitted to OpenMPT...</a>",IDC_SYSLINK1,
-                    "SysLink",WS_TABSTOP,12,144,264,12
-    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,174,276,52
+                    "SysLink",WS_TABSTOP,12,162,264,12
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,192,276,52
     CONTROL         "&Release: Official stable released versions only (recommended)",IDC_RADIO1,
-                    "Button",BS_AUTORADIOBUTTON,12,186,264,10
+                    "Button",BS_AUTORADIOBUTTON,12,204,264,10
     CONTROL         "&Next: Previews of the next official stable release",IDC_RADIO2,
-                    "Button",BS_AUTORADIOBUTTON,12,199,264,10
+                    "Button",BS_AUTORADIOBUTTON,12,217,264,10
     CONTROL         "&Development: Bleeding-edge development versions",IDC_RADIO3,
-                    "Button",BS_AUTORADIOBUTTON,12,212,264,10
+                    "Button",BS_AUTORADIOBUTTON,12,230,264,10
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -402,10 +404,10 @@
 
 IDD_UPDATE DIALOGEX 0, 0, 196, 138
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "OpenMPT Internet Update"
+CAPTION "OpenMPT Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "&Update",IDOK,84,120,50,14
+    DEFPUSHBUTTON   "&Update",IDOK,18,120,116,14
     PUSHBUTTON      "&Cancel",IDCANCEL,138,120,50,14
     LTEXT           "A new version of OpenMPT has been released.",IDC_STATIC,6,6,180,12
     LTEXT           "Your version:",IDC_STATIC,6,24,44,8
@@ -1003,6 +1005,11 @@
     0, 100, 100, 0
 END
 
+IDD_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
+
 #endif    // German (Germany) resources
 /////////////////////////////////////////////////////////////////////////////
 
@@ -2738,7 +2745,7 @@
         MENUITEM "&OpenMPT Website",            ID_NETLINK_MODPLUG
         MENUITEM "&Web Resources",              ID_NETLINK_TOP_PICKS
         MENUITEM SEPARATOR
-        MENUITEM "Check for &Updates",          ID_INTERNETUPDATE
+        MENUITEM "Check for &Updates...",       ID_INTERNETUPDATE
         MENUITEM "&About OpenMPT",              ID_APP_ABOUT
     END
 END
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 13367)
+++ mptrack/resource.h	(working copy)
@@ -981,6 +981,7 @@
 #define IDC_STATIC_RECORDING            2513
 #define IDC_COMBO_RECORDING_CHANNELS    2514
 #define IDC_COMBO_RECORDING_SOURCE      2515
+#define IDC_CHECK_UPDATEINSTALLAUTOMATICALLY 2516
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1287,7 +1288,7 @@
 #define _APS_3D_CONTROLS                     1
 #define _APS_NEXT_RESOURCE_VALUE        543
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2516
+#define _APS_NEXT_CONTROL_VALUE         2517
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 13367)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -312,14 +312,19 @@
 	, vstHostVendorVersion(conf, U_("VST Plugins"), U_("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
 	, UpdateEnabled(conf, U_("Update"), U_("Enabled"), true)
+	, UpdateInstallAutomatically(conf, U_("Update"), U_("InstallAutomatically"), false)
 	, UpdateLastUpdateCheck(conf, U_("Update"), U_("LastUpdateCheck"), mpt::Date::Unix(time_t()))
 	, UpdateUpdateCheckPeriod_DEPRECATED(conf, U_("Update"), U_("UpdateCheckPeriod"), 7)
 	, UpdateIntervalDays(conf, U_("Update"), U_("UpdateCheckIntervalDays"), 7)
 	, UpdateChannel(conf, U_("Update"), U_("Channel"), UpdateChannelRelease)
+#if MPT_UPDATE_LEGACY
 	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
 	, UpdateChannelReleaseURL(conf, U_("Update"), U_("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
 	, UpdateChannelNextURL(conf, U_("Update"), U_("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
 	, UpdateChannelDevelopmentURL(conf, U_("Update"), U_("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+#else // !MPT_UPDATE_LEGACY
+	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), U_("https://update.openmpt.org/check/$VERSION/$GUID"))
+#endif // MPT_UPDATE_LEGACY
 	, UpdateAPIURL(conf, U_("Update"), U_("APIURL"), CUpdateCheck::GetDefaultAPIURL())
 	, UpdateStatisticsConsentAsked(conf, U_("Update"), U_("StatistisConsentAsked"), false)
 	, UpdateStatistics(conf, U_("Update"), U_("Statistis"), false)
@@ -326,6 +331,9 @@
 	, UpdateSendGUID_DEPRECATED(conf, U_("Update"), U_("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, U_("Update"), U_("ShowUpdateHint"), true)
 	, UpdateIgnoreVersion(conf, U_("Update"), U_("IgnoreVersion"), _T(""))
+	, UpdateExperimentalNewAutoUpdate(conf, U_("Update"), U_("ExperimentalNewAutoUpdate"), false)
+	, UpdateSkipSignatureVerificationUNSECURE(conf, U_("Update"), U_("SkipSignatureVerification"), false)
+	, UpdateSigningKeysRootAnchors(conf, U_("Update"), U_("SigningKeysRootAnchors"), CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors())
 	// Wine suppport
 	, WineSupportEnabled(conf, U_("WineSupport"), U_("Enabled"), false)
 	, WineSupportAlwaysRecompile(conf, U_("WineSupport"), U_("AlwaysRecompile"), false)
@@ -673,7 +681,9 @@
 		} else
 		{
 			UpdateChannel = UpdateChannelDevelopment;
+#if MPT_UPDATE_LEGACY
 			UpdateChannelDevelopmentURL = url;
+#endif // MPT_UPDATE_LEGACY
 		}
 		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
 		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 13367)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -839,14 +839,17 @@
 	// Update
 
 	Setting<bool> UpdateEnabled;
+	Setting<bool> UpdateInstallAutomatically;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
 	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
 	Setting<int32> UpdateIntervalDays;
 	Setting<uint32> UpdateChannel;
 	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+#if MPT_UPDATE_LEGACY
 	Setting<mpt::ustring> UpdateChannelReleaseURL;
 	Setting<mpt::ustring> UpdateChannelNextURL;
 	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+#endif // MPT_UPDATE_LEGACY
 	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateStatisticsConsentAsked;
 	Setting<bool> UpdateStatistics;
@@ -853,6 +856,9 @@
 	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<CString> UpdateIgnoreVersion;
+	Setting<bool> UpdateExperimentalNewAutoUpdate;
+	Setting<bool> UpdateSkipSignatureVerificationUNSECURE;
+	Setting<std::vector<mpt::ustring>> UpdateSigningKeysRootAnchors;
 
 	// Wine support
 
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 13367)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -20,14 +20,298 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "../common/mptOSError.h"
+#include "../common/mptCrypto.h"
 #include "HTTP.h"
 #include "../misc/JSON.h"
 #include "dlg_misc.h"
-#include "..//sounddev/SoundDeviceManager.h"
+#include "../sounddev/SoundDeviceManager.h"
+#include "ProgressDialog.h"
+#include "Moddoc.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
 
+
+
+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;
+	};
+	MPT_JSON_INLINE(Update::windowsversion, {
+		MPT_JSON_MAP(version_major);
+		MPT_JSON_MAP(version_minor);
+		MPT_JSON_MAP(servicepack_major);
+		MPT_JSON_MAP(servicepack_minor);
+		MPT_JSON_MAP(build);
+		MPT_JSON_MAP(wine_major);
+		MPT_JSON_MAP(wine_minor);
+		MPT_JSON_MAP(wine_update);
+	})
+
+	struct autoupdate_installer {
+		std::vector<mpt::ustring> arguments = {};
+	};
+	MPT_JSON_INLINE(Update::autoupdate_installer, {
+		MPT_JSON_MAP(arguments);
+	})
+
+	struct autoupdate_archive {
+		mpt::ustring subfolder = U_("");
+		mpt::ustring restartbinary = U_("");
+	};
+	MPT_JSON_INLINE(Update::autoupdate_archive, {
+		MPT_JSON_MAP(subfolder);
+		MPT_JSON_MAP(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;
+	};
+	MPT_JSON_INLINE(Update::downloadinfo, {
+		MPT_JSON_MAP(url);
+		MPT_JSON_MAP(checksums);
+		MPT_JSON_MAP(filename);
+		MPT_JSON_MAP(autoupdate_installer);
+		MPT_JSON_MAP(autoupdate_archive);
+	})
+
+	struct download {
+		mpt::ustring 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 = {};
+	};
+	MPT_JSON_INLINE(Update::download, {
+		MPT_JSON_MAP(url);
+		MPT_JSON_MAP(type);
+		MPT_JSON_MAP(can_autoupdate);
+		MPT_JSON_MAP(autoupdate_minversion);
+		MPT_JSON_MAP(os);
+		MPT_JSON_MAP(required_windows_version);
+		MPT_JSON_MAP(required_architectures);
+		MPT_JSON_MAP(supported_architectures);
+		MPT_JSON_MAP(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 = {};
+	};
+	MPT_JSON_INLINE(Update::versioninfo, {
+		MPT_JSON_MAP(version);
+		MPT_JSON_MAP(date);
+		MPT_JSON_MAP(announcement_url);
+		MPT_JSON_MAP(changelog_url);
+		MPT_JSON_MAP(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::Windows::Name(mpt::Windows::GetProcessArchitecture()) == architecture;
+}
+
+static bool IsArchitectureSupported(const mpt::ustring &architecture)
+{
+	const auto & architectures = mpt::Windows::GetSupportedProcessArchitectures(mpt::Windows::GetHostArchitecture());
+	for(const auto & arch : architectures)
+	{
+		if(mpt::Windows::Name(arch) == architecture)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+static bool IsArchitectureFeatureSupported(const mpt::ustring &architecture, const mpt::ustring &feature)
+{
+	MPT_UNUSED_VARIABLE(architecture);
+	#ifdef ENABLE_ASM
+		if(feature == U_("")) return true;
+		else if(feature == U_("lm")) return (CPU::GetAvailableFeatures() & CPU::feature::lm) != 0;
+		else if(feature == U_("mmx")) return (CPU::GetAvailableFeatures() & CPU::feature::mmx) != 0;
+		else if(feature == U_("sse")) return (CPU::GetAvailableFeatures() & CPU::feature::sse) != 0;
+		else if(feature == U_("sse2")) return (CPU::GetAvailableFeatures() & CPU::feature::sse2) != 0;
+		else if(feature == U_("sse3")) return (CPU::GetAvailableFeatures() & CPU::feature::sse3) != 0;
+		else if(feature == U_("ssse3")) return (CPU::GetAvailableFeatures() & CPU::feature::ssse3) != 0;
+		else if(feature == U_("sse4.1")) return (CPU::GetAvailableFeatures() & CPU::feature::sse4_1) != 0;
+		else if(feature == U_("sse4.2")) return (CPU::GetAvailableFeatures() & CPU::feature::sse4_2) != 0;
+		else if(feature == U_("avx")) return (CPU::GetAvailableFeatures() & CPU::feature::avx) != 0;
+		else if(feature == U_("avx2")) return (CPU::GetAvailableFeatures() & CPU::feature::avx2) != 0;
+		else return false;
+	#else
+		return true;
+	#endif
+}
+
+
+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::Windows::Version::Current().IsBefore(
+					mpt::Windows::Version::System(mpt::saturate_cast<uint32>(download.required_windows_version->version_major), mpt::saturate_cast<uint32>(download.required_windows_version->version_minor)),
+					mpt::Windows::Version::ServicePack(mpt::saturate_cast<uint16>(download.required_windows_version->servicepack_major), mpt::saturate_cast<uint16>(download.required_windows_version->servicepack_minor)),
+					mpt::Windows::Version::Build(mpt::saturate_cast<uint32>(download.required_windows_version->build))
+				))
+			{
+				download_supported = false;
+			}
+
+			if(mpt::Windows::IsWine() && theApp.GetWineVersion()->Version().IsValid())
+			{
+				if(theApp.GetWineVersion()->Version().IsBefore(mpt::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
 {
@@ -35,14 +319,16 @@
 	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)
+	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
@@ -49,6 +335,8 @@
 	{
 		CDialog::OnInitDialog();
 
+		SetDlgItemText(IDOK, m_buttonText);
+
 		CFont *font = GetDlgItem(IDC_VERSION2)->GetFont();
 		LOGFONT lf;
 		font->GetLogFont(&lf);
@@ -56,7 +344,7 @@
 		m_boldFont.CreateFontIndirect(&lf);
 		GetDlgItem(IDC_VERSION2)->SetFont(&m_boldFont);
 
-		SetDlgItemText(IDC_VERSION1, mpt::cfmt::val(Version::Current()));
+		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>"));
@@ -103,6 +391,8 @@
 }
 
 
+#if MPT_UPDATE_LEGACY
+
 mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return U_("https://update.openmpt.org/check/$VERSION/$GUID");
@@ -118,7 +408,21 @@
 	return U_("https://update.openmpt.org/check/testing/$VERSION/$GUID");
 }
 
+#endif // MPT_UPDATE_LEGACY
 
+
+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://sagamusix.de/openmpt-update/"),
+		U_("https://manx.datengang.de/openmpt/update/")
+	};
+}
+
+
 mpt::ustring CUpdateCheck::GetDefaultAPIURL()
 {
 	return U_("https://update.openmpt.org/api/v3/");
@@ -134,9 +438,18 @@
 }
 
 
+
+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)
@@ -143,6 +456,10 @@
 		{
 			return;
 		}
+		if(!IsSuitableUpdateMoment())
+		{
+			return;
+		}
 		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
 		if(updateCheckPeriod < 0)
 		{
@@ -155,7 +472,15 @@
 		const double secsSinceLastCheck = difftime(now, lastCheck);
 		if(secsSinceLastCheck > 0.0 && secsSinceLastCheck < updateCheckPeriod * 86400.0)
 		{
-			return;
+#if MPT_UPDATE_LEGACY
+			if(!TrackerSettings::Instance().UpdateExperimentalNewAutoUpdate)
+			{
+				return;
+			} else
+#endif // MPT_UPDATE_LEGACY
+			{
+				loadPersisted = true;
+			}
 		}
 
 		// Never ran update checks before, so we notify the user of automatic update checks.
@@ -169,7 +494,7 @@
 					checkIntervalDays == 1 ? CString(_T("every day")) :
 					MPT_CFORMAT("every {} days")(checkIntervalDays)
 				);
-			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
+			if(Reporting::Confirm(msg, _T("OpenMPT Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
 				return;
@@ -177,9 +502,14 @@
 		}
 	} 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 Internet Update")) != cnfYes)
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Update")) != cnfYes)
 			{
 				return;
 			}
@@ -207,10 +537,13 @@
 
 	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.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
 	context.autoUpdate = isAutoUpdate;
+	context.loadPersisted = loadPersisted;
 	context.statistics = GetStatisticsDataV3(CUpdateCheck::Settings());
 	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
@@ -218,10 +551,14 @@
 
 CUpdateCheck::Settings::Settings()
 	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
-	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channel(static_cast<UpdateChannel>(TrackerSettings::Instance().UpdateChannel.Get()))
+	, persistencePath(theApp.GetConfigPath())
+#if MPT_UPDATE_LEGACY
+	, modeLegacy(!TrackerSettings::Instance().UpdateExperimentalNewAutoUpdate)
 	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
 	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
 	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+#endif // MPT_UPDATE_LEGACY
 	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
 	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
 	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
@@ -240,7 +577,7 @@
 void CUpdateCheck::ThreadFunc::operator () ()
 {
 	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings, context);
+	CheckForUpdate(settings, context);
 }
 
 
@@ -248,7 +585,6 @@
 {
 	JSON::value j;
 	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
-	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
 	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
 	j["Update"]["PeriodDays"] = settings.periodDays;
 	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
@@ -315,6 +651,7 @@
 }
 
 
+#if MPT_UPDATE_LEGACY
 mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
 {
 	mpt::ustring updateURL;
@@ -360,24 +697,126 @@
 	updateURL = mpt::String::Replace(updateURL, U_("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : U_("anonymous"));
 	return updateURL;
 }
+#endif // MPT_UPDATE_LEGACY
 
 
 // Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings, const std::string &statistics)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Context &context, const CUpdateCheck::Settings &settings, const std::string &statistics)
 {
-	
+	CUpdateCheck::Result 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, 20))
+	{
+		throw CUpdateCheck::Cancel();
+	}
 	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 40))
+	{
+		throw CUpdateCheck::Cancel();
+	}
+#if MPT_UPDATE_LEGACY
+	if(settings.modeLegacy)
+	{
+		result = SearchUpdateLegacy(internet, settings);
+	} else
+#endif // MPT_UPDATE_LEGACY
+	{
+		bool loaded = false;
+		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(!loaded)
+		{
+			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, 60))
+	{
+		throw CUpdateCheck::Cancel();
+	}
+	SendStatistics(internet, settings, statistics);
+	if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 80))
+	{
+		throw CUpdateCheck::Cancel();
+	}
+	CleanOldUpdates(settings, context);
+	if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 100))
+	{
+		throw CUpdateCheck::Cancel();
+	}
+	return result;
+}
 
-	// Establish a connection.
-	HTTP::Request request;
-	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
-	request.method = HTTP::Method::Get;
-	request.flags = HTTP::NoCache;
 
-	HTTP::Result resultHTTP = internet(request);
+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)
 	{
+		if(!settings.modeLegacy)
+		{
+			HTTP::Request requestLegacyUpdate;
+			requestLegacyUpdate.SetURI(ParseURI(GetUpdateURLV2(settings)));
+			requestLegacyUpdate.method = HTTP::Method::Get;
+			requestLegacyUpdate.flags = HTTP::NoCache;
+			HTTP::Result resultLegacyUpdateHTTP = internet(requestLegacyUpdate);
+		}
 		HTTP::Request requestStatistics;
 		if(settings.statisticsUUID.IsValid())
 		{
@@ -395,7 +834,20 @@
 		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
 		internet(requestStatistics);
 	}
+}
 
+
+#if MPT_UPDATE_LEGACY
+CUpdateCheck::Result CUpdateCheck::SearchUpdateLegacy(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
+{
+
+	HTTP::Request request;
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
+	request.method = HTTP::Method::Get;
+	request.flags = HTTP::NoCache;
+
+	HTTP::Result resultHTTP = internet(request);
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -439,21 +891,61 @@
 		}
 		result.UpdateAvailable = true;
 	}
+
 	return result;
+
 }
+#endif // MPT_UPDATE_LEGACY
 
 
+CUpdateCheck::Result 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;
+
+	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.
+	CUpdateCheck::Result 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);
 	CUpdateCheck::Result result;
-	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
+		context.window->SendMessage(context.msgStart, context.autoUpdate ? 1 : 0, 0);
 		try
 		{
-			result = SearchUpdate(settings, context.statistics);
+			result = SearchUpdate(context, settings, context.statistics);
 		} catch(const bad_uri &e)
 		{
 			throw CUpdateCheck::Error(MPT_CFORMAT("Error parsing update URL: {}")(mpt::get_exception_text<CString>(e)));
@@ -461,6 +953,12 @@
 		{
 			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));
@@ -474,6 +972,12 @@
 }
 
 
+bool CUpdateCheck::IsAutoUpdateFromMessage(WPARAM wparam, LPARAM /* lparam */ )
+{
+	return wparam ? true : false;
+}
+
+
 CUpdateCheck::Result CUpdateCheck::ResultFromMessage(WPARAM /*wparam*/ , LPARAM lparam)
 {
 	const CUpdateCheck::Result &result = *reinterpret_cast<CUpdateCheck::Result*>(lparam);
@@ -488,24 +992,554 @@
 }
 
 
+
+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 = NULL)
+		: 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();
+					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();
+						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
+							{
+								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();
+						}
+					};
+					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 = Util::HexToBin(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::ShowSuccessGUI(WPARAM wparam, LPARAM lparam)
 {
+
 	const CUpdateCheck::Result &result = *reinterpret_cast<CUpdateCheck::Result*>(lparam);
 	bool autoUpdate = wparam != 0;
-	if(result.UpdateAvailable && (!autoUpdate || result.Version != TrackerSettings::Instance().UpdateIgnoreVersion))
+
+	if(result.CheckTime != time_t{})
 	{
-		UpdateDialog dlg(result.Version, result.Date, result.URL);
-		if(dlg.DoModal() == IDOK)
+		TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(result.CheckTime);
+	}
+
+#if MPT_UPDATE_LEGACY
+
+	if(!TrackerSettings::Instance().UpdateExperimentalNewAutoUpdate)
+	{
+		if(result.UpdateAvailable && (!autoUpdate || result.Version != TrackerSettings::Instance().UpdateIgnoreVersion))
 		{
-			CTrackApp::OpenURL(result.URL);
+			UpdateDialog dlg(result.Version, result.Date, result.URL);
+			if(dlg.DoModal() == IDOK)
+			{
+				CTrackApp::OpenURL(result.URL);
+			}
+		} else if(!result.UpdateAvailable && !autoUpdate)
+		{
+			Reporting::Information(U_("You already have the latest version of OpenMPT installed."), U_("OpenMPT Internet Update"));
 		}
-	} else if(!result.UpdateAvailable && !autoUpdate)
+		return;
+	}
+
+#endif // MPT_UPDATE_LEGACY
+
+	Update::versions updateData = nlohmann::json::parse(mpt::buffer_cast<std::string>(result.json)).get<Update::versions>();
+	UpdateInfo updateInfo = GetBestDownload(updateData);
+
+	if(!updateInfo.IsAvailable())
 	{
-		Reporting::Information(U_("You already have the latest version of OpenMPT installed."), U_("OpenMPT Internet Update"));
+		if(!autoUpdate)
+		{
+			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
+	{
+		
+		UpdateDialog dlg(
+			mpt::ToCString(versionInfo.version),
+			mpt::ToCString(versionInfo.date),
+			mpt::ToCString(versionInfo.changelog_url),
+				(!updateInfo.download.empty() && versionInfo.downloads[updateInfo.download].can_autoupdate && (Version::Current() >= Version::Parse(versionInfo.downloads[updateInfo.download].autoupdate_minversion))) ? _T("&Install now...") :
+				(!updateInfo.download.empty()) ? _T("&Download now...") :
+				_T("&View Announcement...")
+			);
+		if(dlg.DoModal() != IDOK)
+		{
+			return;
+		}
+
+		if(!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 if(!updateInfo.download.empty())
+		{
+			CTrackApp::OpenURL(updateInfo.download);
+		} else
+		{
+			CTrackApp::OpenURL(versionInfo.announcement_url);
+		}
+
+	}
+
 }
 
 
+mpt::ustring CUpdateCheck::GetFailureMessage(WPARAM wparam, LPARAM lparam)
+{
+	MPT_UNREFERENCED_PARAMETER(wparam);
+	const CUpdateCheck::Error &error = *reinterpret_cast<CUpdateCheck::Error*>(lparam);
+	return mpt::get_exception_text<mpt::ustring>(error);
+}
+
+
+
 void CUpdateCheck::ShowFailureGUI(WPARAM wparam, LPARAM lparam)
 {
 	const CUpdateCheck::Error &error = *reinterpret_cast<CUpdateCheck::Error*>(lparam);
@@ -512,7 +1546,7 @@
 	bool autoUpdate = wparam != 0;
 	if(!autoUpdate)
 	{
-		Reporting::Error(mpt::get_exception_text<mpt::ustring>(error), U_("OpenMPT Internet Update Error"));
+		Reporting::Error(mpt::get_exception_text<mpt::ustring>(error), U_("OpenMPT Update Error"));
 	}
 }
 
@@ -538,6 +1572,13 @@
 }
 
 
+
+CUpdateCheck::Cancel::Cancel()
+{
+	return;
+}
+
+
 /////////////////////////////////////////////////////////////
 // CUpdateSetupDlg
 
@@ -548,6 +1589,7 @@
 	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()
@@ -575,7 +1617,7 @@
 	CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
 
 	int radioID = 0;
-	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
 	if(updateChannel == UpdateChannelRelease)
 	{
 		radioID = IDC_RADIO1;
@@ -629,6 +1671,8 @@
 		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)));
@@ -646,7 +1690,7 @@
 {
 	CUpdateCheck::Settings settings;
 
-	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
 	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
 	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
 	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
@@ -654,14 +1698,43 @@
 	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 = updateChannel;
+	settings.channel = static_cast<UpdateChannel>(updateChannel);
 	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 
 	mpt::ustring statistics;
-	statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
+
+	statistics += U_("Update:") + UL_("\n");
 	statistics += UL_("\n");
+
+#if MPT_UPDATE_LEGACY
+	if(settings.modeLegacy)
+	{
+		statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
+		statistics += UL_("\n");
+	} else
+#endif // MPT_UPDATE_LEGACY
+	{
+		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 MPT_UPDATE_LEGACY
+		if(!settings.modeLegacy)
+#endif // MPT_UPDATE_LEGACY
+		{
+			statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
+			statistics += UL_("\n");
+		}
 		if(settings.statisticsUUID.IsValid())
 		{
 			statistics += U_("PUT ") + settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID) + UL_("\n");
@@ -714,6 +1787,7 @@
 	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);
@@ -731,7 +1805,7 @@
 
 void CUpdateSetupDlg::OnOK()
 {
-	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
 	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
 	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
 	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
@@ -741,6 +1815,7 @@
 	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);
 	
@@ -757,7 +1832,7 @@
 
 void CUpdateSetupDlg::OnCheckNow()
 {
-	CMainFrame::GetMainFrame()->PostMessage(WM_COMMAND, ID_INTERNETUPDATE);
+	CUpdateCheck::DoManualUpdateCheck();
 }
 
 
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 13367)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,7 +24,11 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
-enum UpdateChannel
+namespace HTTP {
+class InternetSession;
+}
+
+enum UpdateChannel : uint32
 {
 	UpdateChannelRelease = 1,
 	UpdateChannelNext = 2,
@@ -42,14 +46,19 @@
 
 	static mpt::ustring GetStatisticsUserInformation(bool shortText);
 
+#if MPT_UPDATE_LEGACY
 	static mpt::ustring GetDefaultChannelReleaseURL();
 	static mpt::ustring GetDefaultChannelNextURL();
 	static mpt::ustring GetDefaultChannelDevelopmentURL();
+#endif // MPT_UPDATE_LEGACY
 
+	static std::vector<mpt::ustring> GetDefaultUpdateSigningKeysRootAnchors();
 	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
+	static bool IsSuitableUpdateMoment();
+
 	static void DoAutoUpdateCheck() { StartUpdateCheckAsync(true); }
 	static void DoManualUpdateCheck() { StartUpdateCheckAsync(false); }
 
@@ -58,10 +67,13 @@
 	struct Context
 	{
 		CWnd *window;
+		UINT msgStart;
 		UINT msgProgress;
 		UINT msgSuccess;
 		UINT msgFailure;
+		UINT msgCanceled;
 		bool autoUpdate;
+		bool loadPersisted;
 		std::string statistics;
 	};
 
@@ -68,10 +80,14 @@
 	struct Settings
 	{
 		int32 periodDays;
-		uint32 channel;
+		UpdateChannel channel;
+		mpt::PathString persistencePath;
+#if MPT_UPDATE_LEGACY
+		bool modeLegacy;
 		mpt::ustring channelReleaseURL;
 		mpt::ustring channelNextURL;
 		mpt::ustring channelDevelopmentURL;
+#endif // MPT_UPDATE_LEGACY
 		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
@@ -88,21 +104,35 @@
 		static CString FormatErrorCode(CString errorMessage, DWORD errorCode);
 	};
 
+	class Cancel
+		: public std::exception
+	{
+	public:
+		Cancel();
+	};
+
 	struct Result
 	{
 		time_t CheckTime;
+		std::vector<std::byte> json;
+#if MPT_UPDATE_LEGACY
 		bool UpdateAvailable;
 		CString Version;
 		CString Date;
 		CString URL;
+#endif // MPT_UPDATE_LEGACY
 		Result()
 			: CheckTime(time_t())
+#if MPT_UPDATE_LEGACY
 			, UpdateAvailable(false)
+#endif // MPT_UPDATE_LEGACY
 		{
 			return;
 		}
 	};
 
+	static bool IsAutoUpdateFromMessage(WPARAM wparam, LPARAM lparam);
+
 	static CUpdateCheck::Result ResultFromMessage(WPARAM wparam, LPARAM lparam);
 	static CUpdateCheck::Error ErrorFromMessage(WPARAM wparam, LPARAM lparam);
 
@@ -109,10 +139,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+	static mpt::ustring GetFailureMessage(WPARAM wparam, LPARAM lparam);
+
 public:
 
+#if MPT_UPDATE_LEGACY
 	// v2
 	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+#endif // MPT_UPDATE_LEGACY
 
 	// v3
 	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
@@ -131,8 +165,17 @@
 
 	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
-	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings, const std::string &statistics); // may throw
-	
+	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Context &context, const CUpdateCheck::Settings &settings, const std::string &statistics); // may throw
+
+	static void CleanOldUpdates(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
+
+	static void SendStatistics(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings, const std::string &statistics); // may throw
+
+#if MPT_UPDATE_LEGACY
+	static CUpdateCheck::Result SearchUpdateLegacy(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings); // may throw
+#endif // MPT_UPDATE_LEGACY
+	static CUpdateCheck::Result SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings); // may throw
+
 };
 
 
@@ -142,6 +185,9 @@
 public:
 	CUpdateSetupDlg();
 
+public:
+	void SettingChanged(const SettingPath &changedPath) override;
+
 protected:
 	void DoDataExchange(CDataExchange *pDX) override;
 	BOOL OnInitDialog() override;
@@ -148,8 +194,6 @@
 	void OnOK() override;
 	BOOL OnSetActive() override;
 
-	void SettingChanged(const SettingPath &changedPath) override;
-
 	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
 	afx_msg void OnShowStatisticsData(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/);
autoupdate-v10.patch (75,630 bytes)   
manx

manx

2020-07-27 08:03

administrator   ~0004409

Implemented in r13368.
Disabled by default for now.

manx

manx

2020-12-29 14:29

administrator   ~0004582

Enabled by default in r13995. Forum thread at <https://forum.openmpt.org/index.php?topic=6535.0>.

manx

manx

2020-12-29 14:39

administrator   ~0004583

This patch removes the old update check method. Note that it also removes the old statistics collecting method which we might want to still defer until we have the same kind of analysis tool for the new statistics.

update-remove-legacy-v1.patch (13,935 bytes)   
Index: common/BuildSettings.h
===================================================================
--- common/BuildSettings.h	(revision 13995)
+++ common/BuildSettings.h	(working copy)
@@ -194,8 +194,6 @@
 
 #if defined(MODPLUG_TRACKER)
 
-#define MPT_UPDATE_LEGACY 1
-
 // Enable built-in test suite.
 #if defined(MPT_BUILD_DEBUG) || defined(MPT_BUILD_CHECKED)
 #define ENABLE_TESTS
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 13995)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -321,14 +321,7 @@
 	, UpdateUpdateCheckPeriod_DEPRECATED(conf, U_("Update"), U_("UpdateCheckPeriod"), 7)
 	, UpdateIntervalDays(conf, U_("Update"), U_("UpdateCheckIntervalDays"), 7)
 	, UpdateChannel(conf, U_("Update"), U_("Channel"), UpdateChannelRelease)
-#if MPT_UPDATE_LEGACY
-	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
-	, UpdateChannelReleaseURL(conf, U_("Update"), U_("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
-	, UpdateChannelNextURL(conf, U_("Update"), U_("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
-	, UpdateChannelDevelopmentURL(conf, U_("Update"), U_("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
-#else // !MPT_UPDATE_LEGACY
 	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), U_("https://update.openmpt.org/check/$VERSION/$GUID"))
-#endif // MPT_UPDATE_LEGACY
 	, UpdateAPIURL(conf, U_("Update"), U_("APIURL"), CUpdateCheck::GetDefaultAPIURL())
 	, UpdateStatisticsConsentAsked(conf, U_("Update"), U_("StatistisConsentAsked"), false)
 	, UpdateStatistics(conf, U_("Update"), U_("Statistis"), false)
@@ -335,9 +328,6 @@
 	, UpdateSendGUID_DEPRECATED(conf, U_("Update"), U_("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, U_("Update"), U_("ShowUpdateHint"), true)
 	, UpdateIgnoreVersion(conf, U_("Update"), U_("IgnoreVersion"), _T(""))
-#if MPT_UPDATE_LEGACY
-	, UpdateLegacyMethod(conf, U_("Update"), U_("LegacyMethod"), false)
-#endif // MPT_UPDATE_LEGACY
 	, UpdateSkipSignatureVerificationUNSECURE(conf, U_("Update"), U_("SkipSignatureVerification"), false)
 	, UpdateSigningKeysRootAnchors(conf, U_("Update"), U_("SigningKeysRootAnchors"), CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors())
 	// Wine suppport
@@ -687,9 +677,6 @@
 		} else
 		{
 			UpdateChannel = UpdateChannelDevelopment;
-#if MPT_UPDATE_LEGACY
-			UpdateChannelDevelopmentURL = url;
-#endif // MPT_UPDATE_LEGACY
 		}
 		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
 		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 13995)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -859,11 +859,6 @@
 	Setting<int32> UpdateIntervalDays;
 	Setting<uint32> UpdateChannel;
 	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
-#if MPT_UPDATE_LEGACY
-	Setting<mpt::ustring> UpdateChannelReleaseURL;
-	Setting<mpt::ustring> UpdateChannelNextURL;
-	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
-#endif // MPT_UPDATE_LEGACY
 	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateStatisticsConsentAsked;
 	Setting<bool> UpdateStatistics;
@@ -870,9 +865,6 @@
 	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<CString> UpdateIgnoreVersion;
-#if MPT_UPDATE_LEGACY
-	Setting<bool> UpdateLegacyMethod;
-#endif // MPT_UPDATE_LEGACY
 	Setting<bool> UpdateSkipSignatureVerificationUNSECURE;
 	Setting<std::vector<mpt::ustring>> UpdateSigningKeysRootAnchors;
 
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 13995)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -393,26 +393,6 @@
 }
 
 
-#if MPT_UPDATE_LEGACY
-
-mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
-{
-	return U_("https://update.openmpt.org/check/$VERSION/$GUID");
-}
-
-mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
-{
-	return U_("https://update.openmpt.org/check/next/$VERSION/$GUID");
-}
-
-mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
-{
-	return U_("https://update.openmpt.org/check/testing/$VERSION/$GUID");
-}
-
-#endif // MPT_UPDATE_LEGACY
-
-
 std::vector<mpt::ustring> CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors()
 {
 	// IMPORTANT:
@@ -474,15 +454,7 @@
 		const double secsSinceLastCheck = difftime(now, lastCheck);
 		if(secsSinceLastCheck > 0.0 && secsSinceLastCheck < updateCheckPeriod * 86400.0)
 		{
-#if MPT_UPDATE_LEGACY
-			if(TrackerSettings::Instance().UpdateLegacyMethod)
-			{
-				return;
-			} else
-#endif // MPT_UPDATE_LEGACY
-			{
-				loadPersisted = true;
-			}
+			loadPersisted = true;
 		}
 
 		// Never ran update checks before, so we notify the user of automatic update checks.
@@ -555,12 +527,6 @@
 	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
 	, channel(static_cast<UpdateChannel>(TrackerSettings::Instance().UpdateChannel.Get()))
 	, persistencePath(theApp.GetConfigPath())
-#if MPT_UPDATE_LEGACY
-	, modeLegacy(TrackerSettings::Instance().UpdateLegacyMethod)
-	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
-	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
-	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
-#endif // MPT_UPDATE_LEGACY
 	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
 	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
 	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
@@ -653,55 +619,6 @@
 }
 
 
-#if MPT_UPDATE_LEGACY
-mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
-{
-	mpt::ustring updateURL;
-	if(settings.channel == UpdateChannelRelease)
-	{
-		updateURL = settings.channelReleaseURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelReleaseURL();
-		}
-	}	else if(settings.channel == UpdateChannelNext)
-	{
-		updateURL = settings.channelNextURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelNextURL();
-		}
-	}	else if(settings.channel == UpdateChannelDevelopment)
-	{
-		updateURL = settings.channelDevelopmentURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelDevelopmentURL();
-		}
-	}	else
-	{
-		updateURL = settings.channelReleaseURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelReleaseURL();
-		}
-	}
-	if(updateURL.find(U_("://")) == mpt::ustring::npos)
-	{
-		updateURL = U_("https://") + updateURL;
-	}
-	// Build update URL
-	updateURL = mpt::String::Replace(updateURL, U_("$VERSION"), MPT_UFORMAT("{}-{}-{}")
-		( Version::Current()
-		, BuildVariants().GuessCurrentBuildName()
-		, settings.sendStatistics ? mpt::OS::Windows::Version::Current().GetNameShort() : U_("unknown")
-		));
-	updateURL = mpt::String::Replace(updateURL, U_("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : U_("anonymous"));
-	return updateURL;
-}
-#endif // MPT_UPDATE_LEGACY
-
-
 // Run update check (independent thread)
 CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Context &context, const CUpdateCheck::Settings &settings, const std::string &statistics)
 {
@@ -719,13 +636,7 @@
 	{
 		throw CUpdateCheck::Cancel();
 	}
-#if MPT_UPDATE_LEGACY
-	if(settings.modeLegacy)
 	{
-		result = SearchUpdateLegacy(internet, settings);
-	} else
-#endif // MPT_UPDATE_LEGACY
-	{
 		bool loaded = false;
 		if(context.loadPersisted)
 		{
@@ -811,14 +722,6 @@
 {
 	if(settings.sendStatistics)
 	{
-		if(!settings.modeLegacy)
-		{
-			HTTP::Request requestLegacyUpdate;
-			requestLegacyUpdate.SetURI(ParseURI(GetUpdateURLV2(settings)));
-			requestLegacyUpdate.method = HTTP::Method::Get;
-			requestLegacyUpdate.flags = HTTP::NoCache;
-			HTTP::Result resultLegacyUpdateHTTP = internet(requestLegacyUpdate);
-		}
 		HTTP::Request requestStatistics;
 		if(settings.statisticsUUID.IsValid())
 		{
@@ -839,67 +742,6 @@
 }
 
 
-#if MPT_UPDATE_LEGACY
-CUpdateCheck::Result CUpdateCheck::SearchUpdateLegacy(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
-{
-
-	HTTP::Request request;
-	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
-	request.method = HTTP::Method::Get;
-	request.flags = HTTP::NoCache;
-
-	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.
-	CUpdateCheck::Result result;
-	result.UpdateAvailable = false;
-	result.CheckTime = time(nullptr);
-	CString resultData = mpt::ToCString(mpt::Charset::UTF8, mpt::buffer_cast<std::string>(resultHTTP.Data));
-	if(resultData.CompareNoCase(_T("noupdate")) != 0)
-	{
-		CString token;
-		int parseStep = 0, parsePos = 0;
-		while(!(token = resultData.Tokenize(_T("\n"), parsePos)).IsEmpty())
-		{
-			token.Trim();
-			switch(parseStep++)
-			{
-			case 0:
-				if(token.CompareNoCase(_T("update")) != 0)
-				{
-					throw CUpdateCheck::Error(_T("Could not understand server response. Maybe your version of OpenMPT is too old!"));
-				}
-				break;
-			case 1:
-				result.Version = token;
-				break;
-			case 2:
-				result.Date = token;
-				break;
-			case 3:
-				result.URL = token;
-				break;
-			}
-		}
-		if(parseStep < 4)
-		{
-			throw CUpdateCheck::Error(_T("Could not understand server response. Maybe your version of OpenMPT is too old!"));
-		}
-		result.UpdateAvailable = true;
-	}
-
-	return result;
-
-}
-#endif // MPT_UPDATE_LEGACY
-
-
 CUpdateCheck::Result CUpdateCheck::SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
 {
 
@@ -1450,26 +1292,6 @@
 		TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(result.CheckTime);
 	}
 
-#if MPT_UPDATE_LEGACY
-
-	if(TrackerSettings::Instance().UpdateLegacyMethod)
-	{
-		if(result.UpdateAvailable && (!autoUpdate || result.Version != TrackerSettings::Instance().UpdateIgnoreVersion))
-		{
-			UpdateDialog dlg(result.Version, result.Date, result.URL);
-			if(dlg.DoModal() == IDOK)
-			{
-				CTrackApp::OpenURL(result.URL);
-			}
-		} else if(!result.UpdateAvailable && !autoUpdate)
-		{
-			Reporting::Information(U_("You already have the latest version of OpenMPT installed."), U_("OpenMPT Internet Update"));
-		}
-		return;
-	}
-
-#endif // MPT_UPDATE_LEGACY
-
 	Update::versions updateData = nlohmann::json::parse(mpt::buffer_cast<std::string>(result.json)).get<Update::versions>();
 	UpdateInfo updateInfo = GetBestDownload(updateData);
 
@@ -1708,14 +1530,7 @@
 	statistics += U_("Update:") + UL_("\n");
 	statistics += UL_("\n");
 
-#if MPT_UPDATE_LEGACY
-	if(settings.modeLegacy)
 	{
-		statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
-		statistics += UL_("\n");
-	} else
-#endif // MPT_UPDATE_LEGACY
-	{
 		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;
@@ -1730,13 +1545,6 @@
 	{
 		statistics += U_("Statistics:") + UL_("\n");
 		statistics += UL_("\n");
-#if MPT_UPDATE_LEGACY
-		if(!settings.modeLegacy)
-#endif // MPT_UPDATE_LEGACY
-		{
-			statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
-			statistics += UL_("\n");
-		}
 		if(settings.statisticsUUID.IsValid())
 		{
 			statistics += U_("PUT ") + settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID) + UL_("\n");
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 13995)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -46,12 +46,6 @@
 
 	static mpt::ustring GetStatisticsUserInformation(bool shortText);
 
-#if MPT_UPDATE_LEGACY
-	static mpt::ustring GetDefaultChannelReleaseURL();
-	static mpt::ustring GetDefaultChannelNextURL();
-	static mpt::ustring GetDefaultChannelDevelopmentURL();
-#endif // MPT_UPDATE_LEGACY
-
 	static std::vector<mpt::ustring> GetDefaultUpdateSigningKeysRootAnchors();
 	static mpt::ustring GetDefaultAPIURL();
 	
@@ -82,12 +76,6 @@
 		int32 periodDays;
 		UpdateChannel channel;
 		mpt::PathString persistencePath;
-#if MPT_UPDATE_LEGACY
-		bool modeLegacy;
-		mpt::ustring channelReleaseURL;
-		mpt::ustring channelNextURL;
-		mpt::ustring channelDevelopmentURL;
-#endif // MPT_UPDATE_LEGACY
 		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
@@ -115,17 +103,8 @@
 	{
 		time_t CheckTime;
 		std::vector<std::byte> json;
-#if MPT_UPDATE_LEGACY
-		bool UpdateAvailable;
-		CString Version;
-		CString Date;
-		CString URL;
-#endif // MPT_UPDATE_LEGACY
 		Result()
 			: CheckTime(time_t())
-#if MPT_UPDATE_LEGACY
-			, UpdateAvailable(false)
-#endif // MPT_UPDATE_LEGACY
 		{
 			return;
 		}
@@ -143,11 +122,6 @@
 
 public:
 
-#if MPT_UPDATE_LEGACY
-	// v2
-	static mpt::ustring GetUpdateURLV2(const Settings &settings);
-#endif // MPT_UPDATE_LEGACY
-
 	// v3
 	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
 
@@ -171,9 +145,6 @@
 
 	static void SendStatistics(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings, const std::string &statistics); // may throw
 
-#if MPT_UPDATE_LEGACY
-	static CUpdateCheck::Result SearchUpdateLegacy(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings); // may throw
-#endif // MPT_UPDATE_LEGACY
 	static CUpdateCheck::Result SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings); // may throw
 
 };
update-remove-legacy-v1.patch (13,935 bytes)   
manx

manx

2021-09-24 13:38

administrator   ~0004881

update-remove-legacy-v3.patch (15,666 bytes)   
Index: common/BuildSettings.h
===================================================================
--- common/BuildSettings.h	(revision 15722)
+++ common/BuildSettings.h	(working copy)
@@ -209,8 +209,6 @@
 
 #if defined(MODPLUG_TRACKER)
 
-#define MPT_UPDATE_LEGACY 1
-
 // Enable built-in test suite.
 #if defined(MPT_BUILD_DEBUG) || defined(MPT_BUILD_CHECKED)
 #define ENABLE_TESTS
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 15722)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -326,14 +326,7 @@
 	, UpdateUpdateCheckPeriod_DEPRECATED(conf, U_("Update"), U_("UpdateCheckPeriod"), 7)
 	, UpdateIntervalDays(conf, U_("Update"), U_("UpdateCheckIntervalDays"), 7)
 	, UpdateChannel(conf, U_("Update"), U_("Channel"), UpdateChannelRelease)
-#if MPT_UPDATE_LEGACY
-	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
-	, UpdateChannelReleaseURL(conf, U_("Update"), U_("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
-	, UpdateChannelNextURL(conf, U_("Update"), U_("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
-	, UpdateChannelDevelopmentURL(conf, U_("Update"), U_("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
-#else // !MPT_UPDATE_LEGACY
 	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), U_("https://update.openmpt.org/check/$VERSION/$GUID"))
-#endif // MPT_UPDATE_LEGACY
 	, UpdateAPIURL(conf, U_("Update"), U_("APIURL"), CUpdateCheck::GetDefaultAPIURL())
 	, UpdateStatisticsConsentAsked(conf, U_("Update"), U_("StatistisConsentAsked"), false)
 	, UpdateStatistics(conf, U_("Update"), U_("Statistis"), false)
@@ -340,9 +333,6 @@
 	, UpdateSendGUID_DEPRECATED(conf, U_("Update"), U_("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, U_("Update"), U_("ShowUpdateHint"), true)
 	, UpdateIgnoreVersion(conf, U_("Update"), U_("IgnoreVersion"), _T(""))
-#if MPT_UPDATE_LEGACY
-	, UpdateLegacyMethod(conf, U_("Update"), U_("LegacyMethod"), false)
-#endif // MPT_UPDATE_LEGACY
 	, UpdateSkipSignatureVerificationUNSECURE(conf, U_("Update"), U_("SkipSignatureVerification"), false)
 	, UpdateSigningKeysRootAnchors(conf, U_("Update"), U_("SigningKeysRootAnchors"), CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors())
 #endif // MPT_ENABLE_UPDATE
@@ -822,9 +812,6 @@
 		} else
 		{
 			UpdateChannel = UpdateChannelDevelopment;
-#if MPT_UPDATE_LEGACY
-			UpdateChannelDevelopmentURL = url;
-#endif // MPT_UPDATE_LEGACY
 		}
 		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
 		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 15722)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -901,11 +901,6 @@
 	Setting<int32> UpdateIntervalDays;
 	Setting<uint32> UpdateChannel;
 	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
-#if MPT_UPDATE_LEGACY
-	Setting<mpt::ustring> UpdateChannelReleaseURL;
-	Setting<mpt::ustring> UpdateChannelNextURL;
-	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
-#endif // MPT_UPDATE_LEGACY
 	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateStatisticsConsentAsked;
 	Setting<bool> UpdateStatistics;
@@ -912,9 +907,6 @@
 	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<CString> UpdateIgnoreVersion;
-#if MPT_UPDATE_LEGACY
-	Setting<bool> UpdateLegacyMethod;
-#endif // MPT_UPDATE_LEGACY
 	Setting<bool> UpdateSkipSignatureVerificationUNSECURE;
 	Setting<std::vector<mpt::ustring>> UpdateSigningKeysRootAnchors;
 
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 15722)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -399,26 +399,6 @@
 }
 
 
-#if MPT_UPDATE_LEGACY
-
-mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
-{
-	return U_("https://update.openmpt.org/check/$VERSION/$GUID");
-}
-
-mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
-{
-	return U_("https://update.openmpt.org/check/next/$VERSION/$GUID");
-}
-
-mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
-{
-	return U_("https://update.openmpt.org/check/testing/$VERSION/$GUID");
-}
-
-#endif // MPT_UPDATE_LEGACY
-
-
 std::vector<mpt::ustring> CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors()
 {
 	// IMPORTANT:
@@ -480,15 +460,7 @@
 		const double secsSinceLastCheck = difftime(now, lastCheck);
 		if(secsSinceLastCheck > 0.0 && secsSinceLastCheck < updateCheckPeriod * 86400.0)
 		{
-#if MPT_UPDATE_LEGACY
-			if(TrackerSettings::Instance().UpdateLegacyMethod)
-			{
-				return;
-			} else
-#endif // MPT_UPDATE_LEGACY
-			{
-				loadPersisted = true;
-			}
+			loadPersisted = true;
 		}
 
 		// Never ran update checks before, so we notify the user of automatic update checks.
@@ -561,12 +533,6 @@
 	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
 	, channel(static_cast<UpdateChannel>(TrackerSettings::Instance().UpdateChannel.Get()))
 	, persistencePath(theApp.GetConfigPath())
-#if MPT_UPDATE_LEGACY
-	, modeLegacy(TrackerSettings::Instance().UpdateLegacyMethod)
-	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
-	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
-	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
-#endif // MPT_UPDATE_LEGACY
 	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
 	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
 	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
@@ -661,62 +627,10 @@
 }
 
 
-#if MPT_UPDATE_LEGACY
-mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
-{
-	mpt::ustring updateURL;
-	if(settings.channel == UpdateChannelRelease)
-	{
-		updateURL = settings.channelReleaseURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelReleaseURL();
-		}
-	}	else if(settings.channel == UpdateChannelNext)
-	{
-		updateURL = settings.channelNextURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelNextURL();
-		}
-	}	else if(settings.channel == UpdateChannelDevelopment)
-	{
-		updateURL = settings.channelDevelopmentURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelDevelopmentURL();
-		}
-	}	else
-	{
-		updateURL = settings.channelReleaseURL;
-		if(updateURL.empty())
-		{
-			updateURL = GetDefaultChannelReleaseURL();
-		}
-	}
-	if(updateURL.find(U_("://")) == mpt::ustring::npos)
-	{
-		updateURL = U_("https://") + updateURL;
-	}
-	// Build update URL
-	updateURL = mpt::String::Replace(updateURL, U_("$VERSION"), MPT_UFORMAT("{}-{}-{}")
-		( Version::Current()
-		, BuildVariants().GuessCurrentBuildName()
-		, settings.sendStatistics ? mpt::OS::Windows::Version::Current().GetNameShort() : U_("unknown")
-		));
-	updateURL = mpt::String::Replace(updateURL, U_("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : U_("anonymous"));
-	return updateURL;
-}
-#endif // MPT_UPDATE_LEGACY
-
-
 // Run update check (independent thread)
 UpdateCheckResult CUpdateCheck::SearchUpdate(const CUpdateCheck::Context &context, const CUpdateCheck::Settings &settings, const std::string &statistics)
 {
 	UpdateCheckResult result;
-
-#if MPT_UPDATE_LEGACY
-	if(settings.modeLegacy)
 	{
 		if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 0))
 		{
@@ -726,43 +640,6 @@
 		{
 			throw CUpdateCheck::Cancel();
 		}
-		{
-			HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
-			if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 30))
-			{
-				throw CUpdateCheck::Cancel();
-			}
-			result = SearchUpdateLegacy(internet, settings);
-			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();
-		}
-	} else
-#endif // MPT_UPDATE_LEGACY
-	{
-		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)
@@ -860,17 +737,6 @@
 {
 	if(settings.sendStatistics)
 	{
-		if(!settings.modeLegacy)
-		{
-			HTTP::Request requestLegacyUpdate;
-			requestLegacyUpdate.SetURI(ParseURI(GetUpdateURLV2(settings)));
-			requestLegacyUpdate.method = HTTP::Method::Get;
-			requestLegacyUpdate.flags = HTTP::NoCache;
-#if defined(MPT_BUILD_RETRO)
-			requestLegacyUpdate.InsecureTLSDowngradeWindowsXP();
-#endif // MPT_BUILD_RETRO
-			HTTP::Result resultLegacyUpdateHTTP = internet(requestLegacyUpdate);
-		}
 		HTTP::Request requestStatistics;
 		if(settings.statisticsUUID.IsValid())
 		{
@@ -894,70 +760,6 @@
 }
 
 
-#if MPT_UPDATE_LEGACY
-UpdateCheckResult CUpdateCheck::SearchUpdateLegacy(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
-{
-
-	HTTP::Request request;
-	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
-	request.method = HTTP::Method::Get;
-	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.UpdateAvailable = false;
-	result.CheckTime = time(nullptr);
-	CString resultData = mpt::ToCString(mpt::Charset::UTF8, mpt::buffer_cast<std::string>(resultHTTP.Data));
-	if(resultData.CompareNoCase(_T("noupdate")) != 0)
-	{
-		CString token;
-		int parseStep = 0, parsePos = 0;
-		while(!(token = resultData.Tokenize(_T("\n"), parsePos)).IsEmpty())
-		{
-			token.Trim();
-			switch(parseStep++)
-			{
-			case 0:
-				if(token.CompareNoCase(_T("update")) != 0)
-				{
-					throw CUpdateCheck::Error(_T("Could not understand server response. Maybe your version of OpenMPT is too old!"));
-				}
-				break;
-			case 1:
-				result.Version = token;
-				break;
-			case 2:
-				result.Date = token;
-				break;
-			case 3:
-				result.URL = token;
-				break;
-			}
-		}
-		if(parseStep < 4)
-		{
-			throw CUpdateCheck::Error(_T("Could not understand server response. Maybe your version of OpenMPT is too old!"));
-		}
-		result.UpdateAvailable = true;
-	}
-
-	return result;
-
-}
-#endif // MPT_UPDATE_LEGACY
-
-
 UpdateCheckResult CUpdateCheck::SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
 {
 
@@ -1523,41 +1325,6 @@
 {
 	bool modal = !autoUpdate;
 
-#if MPT_UPDATE_LEGACY
-
-	if(TrackerSettings::Instance().UpdateLegacyMethod)
-	{
-		if(!result.UpdateAvailable)
-		{
-			if(modal)
-			{
-				Reporting::Information(U_("You already have the latest version of OpenMPT installed."), U_("OpenMPT Update"));
-			}
-			return;
-		}
-
-		// always show indicator, do not highlight it with a tooltip if we show a modal window later
-		if(!CMainFrame::GetMainFrame()->ShowUpdateIndicator(result, result.Version, result.URL, !modal))
-		{
-			// on failure to show indicator, continue and show modal dialog
-			modal = true;
-		}
-
-		if(!modal)
-		{
-			return;
-		}
-		UpdateDialog dlg(result.Version, result.Date, result.URL);
-		if(dlg.DoModal() != IDOK)
-		{
-			return;
-		}
-		CTrackApp::OpenURL(result.URL);
-		return;
-	}
-
-#endif // MPT_UPDATE_LEGACY
-
 	Update::versions updateData = nlohmann::json::parse(mpt::buffer_cast<std::string>(result.json)).get<Update::versions>();
 	UpdateInfo updateInfo = GetBestDownload(updateData);
 
@@ -1808,14 +1575,7 @@
 	statistics += U_("Update:") + UL_("\n");
 	statistics += UL_("\n");
 
-#if MPT_UPDATE_LEGACY
-	if(settings.modeLegacy)
 	{
-		statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
-		statistics += UL_("\n");
-	} else
-#endif // MPT_UPDATE_LEGACY
-	{
 		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;
@@ -1830,13 +1590,6 @@
 	{
 		statistics += U_("Statistics:") + UL_("\n");
 		statistics += UL_("\n");
-#if MPT_UPDATE_LEGACY
-		if(!settings.modeLegacy)
-#endif // MPT_UPDATE_LEGACY
-		{
-			statistics += U_("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + UL_("\n");
-			statistics += UL_("\n");
-		}
 		if(settings.statisticsUUID.IsValid())
 		{
 			statistics += U_("PUT ") + settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID) + UL_("\n");
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 15722)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -42,14 +42,10 @@
 {
 	time_t CheckTime = time_t{};
 	std::vector<std::byte> json;
-#if MPT_UPDATE_LEGACY
-	bool UpdateAvailable = false;
-	CString Version;
-	CString Date;
-	CString URL;
-#endif  // MPT_UPDATE_LEGACY
-
-	bool IsFromCache() const noexcept { return CheckTime == time_t{}; }
+	bool IsFromCache() const noexcept
+	{
+		return CheckTime == time_t{};
+	}
 };
 
 class CUpdateCheck
@@ -63,12 +59,6 @@
 
 	static mpt::ustring GetStatisticsUserInformation(bool shortText);
 
-#if MPT_UPDATE_LEGACY
-	static mpt::ustring GetDefaultChannelReleaseURL();
-	static mpt::ustring GetDefaultChannelNextURL();
-	static mpt::ustring GetDefaultChannelDevelopmentURL();
-#endif // MPT_UPDATE_LEGACY
-
 	static std::vector<mpt::ustring> GetDefaultUpdateSigningKeysRootAnchors();
 	static mpt::ustring GetDefaultAPIURL();
 	
@@ -99,12 +89,6 @@
 		int32 periodDays;
 		UpdateChannel channel;
 		mpt::PathString persistencePath;
-#if MPT_UPDATE_LEGACY
-		bool modeLegacy;
-		mpt::ustring channelReleaseURL;
-		mpt::ustring channelNextURL;
-		mpt::ustring channelDevelopmentURL;
-#endif // MPT_UPDATE_LEGACY
 		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
@@ -143,11 +127,6 @@
 
 public:
 
-#if MPT_UPDATE_LEGACY
-	// v2
-	static mpt::ustring GetUpdateURLV2(const Settings &settings);
-#endif // MPT_UPDATE_LEGACY
-
 	// v3
 	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
 
@@ -171,9 +150,6 @@
 
 	static void SendStatistics(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings, const std::string &statistics); // may throw
 
-#if MPT_UPDATE_LEGACY
-	static UpdateCheckResult SearchUpdateLegacy(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings); // may throw
-#endif // MPT_UPDATE_LEGACY
 	static UpdateCheckResult SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings); // may throw
 
 };
update-remove-legacy-v3.patch (15,666 bytes)   

Issue History

Date Modified Username Field Change
2017-08-14 10:24 Saga Musix New Issue
2017-08-14 10:25 Saga Musix Description Updated
2017-08-14 11:27 manx Note Added: 0003167
2017-08-14 11:32 Saga Musix Description Updated
2017-08-14 12:40 Saga Musix Description Updated
2017-08-14 12:42 Saga Musix Description Updated
2017-08-16 22:51 Saga Musix Description Updated
2017-09-22 15:10 manx Note Added: 0003230
2017-09-22 15:28 Saga Musix Note Added: 0003232
2017-09-22 15:38 manx Note Added: 0003233
2018-05-27 06:22 manx Relationship added related to 0001123
2018-08-22 10:55 manx File Added: Screenshot at 2018-01-15 11:58:45.png
2018-08-22 10:55 manx Note Added: 0003605
2018-08-22 16:03 manx File Added: updatedialog-v2.png
2018-08-22 16:05 manx File Added: update-statistics-v2.patch
2018-09-12 06:12 manx Relationship added related to 0000649
2018-09-12 13:46 manx File Added: update-statistics-v3.patch
2018-11-04 15:39 manx File Added: update-statistics-v5.patch
2018-11-09 09:12 manx File Added: update-statistics-v7.patch
2018-11-09 09:12 manx Note Added: 0003708
2018-11-11 15:49 manx Note Added: 0003712
2019-01-03 12:46 manx Relationship added related to 0001185
2019-01-07 15:14 manx Relationship added related to 0001186
2019-03-08 12:55 manx Relationship added related to 0001213
2019-03-08 15:34 manx Relationship added related to 0001214
2020-05-10 09:06 manx Target Version OpenMPT 1.?? (long term goals) => OpenMPT 1.30.01.00 / libopenmpt 0.6.0 (upgrade first)
2020-06-07 07:32 manx Note Added: 0004373
2020-06-07 07:35 manx Assigned To => manx
2020-06-07 07:35 manx Status new => confirmed
2020-06-07 07:36 manx Status confirmed => assigned
2020-07-27 07:59 manx Note Added: 0004408
2020-07-27 07:59 manx File Added: autoupdate-v10.patch
2020-07-27 08:03 manx Note Added: 0004409
2020-07-27 08:03 manx Status assigned => feedback
2020-10-08 09:35 manx Relationship added related to 0001377
2020-12-29 14:29 manx Note Added: 0004582
2020-12-29 14:39 manx Note Added: 0004583
2020-12-29 14:39 manx File Added: update-remove-legacy-v1.patch
2021-01-10 18:28 manx Status feedback => resolved
2021-01-10 18:28 manx Resolution open => fixed
2021-01-10 18:28 manx Fixed in Version => OpenMPT 1.30.01.00 / libopenmpt 0.6.0 (upgrade first)
2021-03-01 15:32 manx Relationship added related to 0001428
2021-06-01 10:59 manx Relationship added related to 0001466
2021-09-24 13:38 manx Note Added: 0004881
2021-09-24 13:38 manx File Added: update-remove-legacy-v3.patch
2021-12-25 18:20 manx Relationship added related to 0001532