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*/);
