1
0

UpdateCheck.cpp 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699
  1. /*
  2. * UpdateCheck.cpp
  3. * ---------------
  4. * Purpose: Class for easy software update check.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "UpdateCheck.h"
  11. #include "mpt/binary/hex.hpp"
  12. #include "BuildVariants.h"
  13. #include "../common/version.h"
  14. #include "../common/misc_util.h"
  15. #include "../common/mptStringBuffer.h"
  16. #include "Mptrack.h"
  17. #include "TrackerSettings.h"
  18. // Setup dialog stuff
  19. #include "Mainfrm.h"
  20. #include "mpt/system_error/system_error.hpp"
  21. #include "mpt/crypto/hash.hpp"
  22. #include "mpt/crypto/jwk.hpp"
  23. #include "HTTP.h"
  24. #include "mpt/json/json.hpp"
  25. #include "dlg_misc.h"
  26. #include "openmpt/sounddevice/SoundDeviceManager.hpp"
  27. #include "ProgressDialog.h"
  28. #include "Moddoc.h"
  29. #include "mpt/io/io.hpp"
  30. #include "mpt/io/io_stdstream.hpp"
  31. OPENMPT_NAMESPACE_BEGIN
  32. #if defined(MPT_ENABLE_UPDATE)
  33. namespace Update {
  34. struct windowsversion {
  35. uint64 version_major = 0;
  36. uint64 version_minor = 0;
  37. uint64 servicepack_major = 0;
  38. uint64 servicepack_minor = 0;
  39. uint64 build = 0;
  40. uint64 wine_major = 0;
  41. uint64 wine_minor = 0;
  42. uint64 wine_update = 0;
  43. };
  44. NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(windowsversion
  45. ,version_major
  46. ,version_minor
  47. ,servicepack_major
  48. ,servicepack_minor
  49. ,build
  50. ,wine_major
  51. ,wine_minor
  52. ,wine_update
  53. )
  54. struct autoupdate_installer {
  55. std::vector<mpt::ustring> arguments = {};
  56. };
  57. NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(autoupdate_installer
  58. ,arguments
  59. )
  60. struct autoupdate_archive {
  61. mpt::ustring subfolder = U_("");
  62. mpt::ustring restartbinary = U_("");
  63. };
  64. NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(autoupdate_archive
  65. ,subfolder
  66. ,restartbinary
  67. )
  68. struct downloadinfo {
  69. mpt::ustring url = U_("");
  70. std::map<mpt::ustring, mpt::ustring> checksums = {};
  71. mpt::ustring filename = U_("");
  72. std::optional<autoupdate_installer> autoupdate_installer;
  73. std::optional<autoupdate_archive> autoupdate_archive;
  74. };
  75. NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(downloadinfo
  76. ,url
  77. ,checksums
  78. ,filename
  79. ,autoupdate_installer
  80. ,autoupdate_archive
  81. )
  82. struct download {
  83. mpt::ustring url = U_("");
  84. mpt::ustring download_url = U_("");
  85. mpt::ustring type = U_("");
  86. bool can_autoupdate = false;
  87. mpt::ustring autoupdate_minversion = U_("");
  88. mpt::ustring os = U_("");
  89. std::optional<windowsversion> required_windows_version;
  90. std::map<mpt::ustring, bool> required_architectures = {};
  91. std::map<mpt::ustring, bool> supported_architectures = {};
  92. std::map<mpt::ustring, std::map<mpt::ustring, bool>> required_processor_features = {};
  93. };
  94. NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(download
  95. ,url
  96. ,download_url
  97. ,type
  98. ,can_autoupdate
  99. ,autoupdate_minversion
  100. ,os
  101. ,required_windows_version
  102. ,required_architectures
  103. ,supported_architectures
  104. ,required_processor_features
  105. )
  106. struct versioninfo {
  107. mpt::ustring version = U_("");
  108. mpt::ustring date = U_("");
  109. mpt::ustring announcement_url = U_("");
  110. mpt::ustring changelog_url = U_("");
  111. std::map<mpt::ustring, download> downloads = {};
  112. };
  113. NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(versioninfo
  114. ,version
  115. ,date
  116. ,announcement_url
  117. ,changelog_url
  118. ,downloads
  119. )
  120. using versions = std::map<mpt::ustring, versioninfo>;
  121. } // namespace Update
  122. struct UpdateInfo {
  123. mpt::ustring version;
  124. mpt::ustring download;
  125. bool IsAvailable() const
  126. {
  127. return !version.empty();
  128. }
  129. };
  130. static bool IsCurrentArchitecture(const mpt::ustring &architecture)
  131. {
  132. return mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()) == architecture;
  133. }
  134. static bool IsArchitectureSupported(const mpt::ustring &architecture)
  135. {
  136. const auto & architectures = mpt::OS::Windows::GetSupportedProcessArchitectures(mpt::OS::Windows::GetHostArchitecture());
  137. for(const auto & arch : architectures)
  138. {
  139. if(mpt::OS::Windows::Name(arch) == architecture)
  140. {
  141. return true;
  142. }
  143. }
  144. return false;
  145. }
  146. static bool IsArchitectureFeatureSupported(const mpt::ustring &architecture, const mpt::ustring &feature)
  147. {
  148. MPT_UNUSED_VARIABLE(architecture);
  149. #ifdef MPT_ENABLE_ARCH_INTRINSICS
  150. const CPU::Info CPUInfo = CPU::Info::Get();
  151. if(feature == U_("")) return true;
  152. else if(feature == U_("lm")) return (CPUInfo.AvailableFeatures & CPU::feature::lm) != 0;
  153. else if(feature == U_("mmx")) return (CPUInfo.AvailableFeatures & CPU::feature::mmx) != 0;
  154. else if(feature == U_("sse")) return (CPUInfo.AvailableFeatures & CPU::feature::sse) != 0;
  155. else if(feature == U_("sse2")) return (CPUInfo.AvailableFeatures & CPU::feature::sse2) != 0;
  156. else if(feature == U_("sse3")) return (CPUInfo.AvailableFeatures & CPU::feature::sse3) != 0;
  157. else if(feature == U_("ssse3")) return (CPUInfo.AvailableFeatures & CPU::feature::ssse3) != 0;
  158. else if(feature == U_("sse4.1")) return (CPUInfo.AvailableFeatures & CPU::feature::sse4_1) != 0;
  159. else if(feature == U_("sse4.2")) return (CPUInfo.AvailableFeatures & CPU::feature::sse4_2) != 0;
  160. else if(feature == U_("avx")) return (CPUInfo.AvailableFeatures & CPU::feature::avx) != 0;
  161. else if(feature == U_("avx2")) return (CPUInfo.AvailableFeatures & CPU::feature::avx2) != 0;
  162. else return false;
  163. #else // !MPT_ENABLE_ARCH_INTRINSICS
  164. return true;
  165. #endif // MPT_ENABLE_ARCH_INTRINSICS
  166. }
  167. static mpt::ustring GetChannelName(UpdateChannel channel)
  168. {
  169. mpt::ustring channelName = U_("release");
  170. switch(channel)
  171. {
  172. case UpdateChannelDevelopment:
  173. channelName = U_("development");
  174. break;
  175. case UpdateChannelNext:
  176. channelName = U_("next");
  177. break;
  178. case UpdateChannelRelease:
  179. channelName = U_("release");
  180. break;
  181. default:
  182. channelName = U_("release");
  183. break;
  184. }
  185. return channelName;
  186. }
  187. static UpdateInfo GetBestDownload(const Update::versions &versions)
  188. {
  189. UpdateInfo result;
  190. VersionWithRevision bestVersion = VersionWithRevision::Current();
  191. for(const auto & [versionname, versioninfo] : versions)
  192. {
  193. if(!VersionWithRevision::Parse(versioninfo.version).IsNewerThan(bestVersion))
  194. {
  195. continue;
  196. }
  197. mpt::ustring bestDownloadName;
  198. // check if version supports the current system
  199. bool is_supported = false;
  200. for(auto & [downloadname, download] : versioninfo.downloads)
  201. {
  202. // is it for windows?
  203. if(download.os != U_("windows") || !download.required_windows_version)
  204. {
  205. continue;
  206. }
  207. // can the installer run on the current system?
  208. bool download_supported = true;
  209. for(const auto & [architecture, required] : download.required_architectures)
  210. {
  211. if(!(required && IsArchitectureSupported(architecture)))
  212. {
  213. download_supported = false;
  214. }
  215. }
  216. // does the download run on current architecture?
  217. bool architecture_supported = false;
  218. for(const auto & [architecture, supported] : download.supported_architectures)
  219. {
  220. if(supported && IsCurrentArchitecture(architecture))
  221. {
  222. architecture_supported = true;
  223. }
  224. }
  225. if(!architecture_supported)
  226. {
  227. download_supported = false;
  228. }
  229. // does the current system have all required features?
  230. for(const auto & [architecture, features] : download.required_processor_features)
  231. {
  232. if(IsCurrentArchitecture(architecture))
  233. {
  234. for(const auto & [feature, required] : features)
  235. {
  236. if(!(required && IsArchitectureFeatureSupported(architecture, feature)))
  237. {
  238. download_supported = false;
  239. }
  240. }
  241. }
  242. }
  243. if(mpt::OS::Windows::Version::Current().IsBefore(
  244. mpt::osinfo::windows::Version::System(mpt::saturate_cast<uint32>(download.required_windows_version->version_major), mpt::saturate_cast<uint32>(download.required_windows_version->version_minor)),
  245. mpt::osinfo::windows::Version::ServicePack(mpt::saturate_cast<uint16>(download.required_windows_version->servicepack_major), mpt::saturate_cast<uint16>(download.required_windows_version->servicepack_minor)),
  246. mpt::osinfo::windows::Version::Build(mpt::saturate_cast<uint32>(download.required_windows_version->build))
  247. ))
  248. {
  249. download_supported = false;
  250. }
  251. if(mpt::OS::Windows::IsWine() && theApp.GetWineVersion()->Version().IsValid())
  252. {
  253. if(theApp.GetWineVersion()->Version().IsBefore(mpt::OS::Wine::Version(mpt::saturate_cast<uint8>(download.required_windows_version->wine_major), mpt::saturate_cast<uint8>(download.required_windows_version->wine_minor), mpt::saturate_cast<uint8>(download.required_windows_version->wine_update))))
  254. {
  255. download_supported = false;
  256. }
  257. }
  258. if(download_supported)
  259. {
  260. is_supported = true;
  261. if(theApp.IsInstallerMode() && download.type == U_("installer"))
  262. {
  263. bestDownloadName = downloadname;
  264. } else if(theApp.IsPortableMode() && download.type == U_("archive"))
  265. {
  266. bestDownloadName = downloadname;
  267. }
  268. }
  269. }
  270. if(is_supported)
  271. {
  272. bestVersion = VersionWithRevision::Parse(versioninfo.version);
  273. result.version = versionname;
  274. result.download = bestDownloadName;
  275. }
  276. }
  277. return result;
  278. }
  279. // Update notification dialog
  280. class UpdateDialog : public CDialog
  281. {
  282. protected:
  283. const CString m_releaseVersion;
  284. const CString m_releaseDate;
  285. const CString m_releaseURL;
  286. const CString m_buttonText;
  287. CFont m_boldFont;
  288. public:
  289. UpdateDialog(const CString &releaseVersion, const CString &releaseDate, const CString &releaseURL, const CString &buttonText = _T("&Update"))
  290. : CDialog(IDD_UPDATE)
  291. , m_releaseVersion(releaseVersion)
  292. , m_releaseDate(releaseDate)
  293. , m_releaseURL(releaseURL)
  294. , m_buttonText(buttonText)
  295. { }
  296. BOOL OnInitDialog() override
  297. {
  298. CDialog::OnInitDialog();
  299. SetDlgItemText(IDOK, m_buttonText);
  300. CFont *font = GetDlgItem(IDC_VERSION2)->GetFont();
  301. LOGFONT lf;
  302. font->GetLogFont(&lf);
  303. lf.lfWeight = FW_BOLD;
  304. m_boldFont.CreateFontIndirect(&lf);
  305. GetDlgItem(IDC_VERSION2)->SetFont(&m_boldFont);
  306. SetDlgItemText(IDC_VERSION1, mpt::cfmt::val(VersionWithRevision::Current()));
  307. SetDlgItemText(IDC_VERSION2, m_releaseVersion);
  308. SetDlgItemText(IDC_DATE, m_releaseDate);
  309. SetDlgItemText(IDC_SYSLINK1, _T("More information about this build:\n<a href=\"") + m_releaseURL + _T("\">") + m_releaseURL + _T("</a>"));
  310. CheckDlgButton(IDC_CHECK1, (TrackerSettings::Instance().UpdateIgnoreVersion == m_releaseVersion) ? BST_CHECKED : BST_UNCHECKED);
  311. return FALSE;
  312. }
  313. void OnDestroy()
  314. {
  315. TrackerSettings::Instance().UpdateIgnoreVersion = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED ? m_releaseVersion : CString();
  316. m_boldFont.DeleteObject();
  317. CDialog::OnDestroy();
  318. }
  319. void OnClickURL(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
  320. {
  321. CTrackApp::OpenURL(m_releaseURL);
  322. }
  323. DECLARE_MESSAGE_MAP()
  324. };
  325. BEGIN_MESSAGE_MAP(UpdateDialog, CDialog)
  326. ON_NOTIFY(NM_CLICK, IDC_SYSLINK1, &UpdateDialog::OnClickURL)
  327. ON_WM_DESTROY()
  328. END_MESSAGE_MAP()
  329. mpt::ustring CUpdateCheck::GetStatisticsUserInformation(bool shortText)
  330. {
  331. if(shortText)
  332. {
  333. return U_("A randomized user ID is sent together with basic system information."
  334. " This ID cannot be linked to you personally in any way."
  335. "\nOpenMPT will use this information to gather usage statistics and to plan system support for future OpenMPT versions.");
  336. } else
  337. {
  338. return U_(
  339. "When checking for updates, OpenMPT can additionally collect basic statistical information."
  340. " A randomized user ID is sent alongside the update check. This ID and the transmitted statistics cannot be linked to you personally in any way."
  341. " OpenMPT will use this information to gather usage statistics and to plan system support for future OpenMPT versions."
  342. "\nOpenMPT would collect the following statistical data points: OpenMPT version, Windows version, type of CPU, amount of RAM, sound device settings, configured update check frequency of OpenMPT.");
  343. }
  344. }
  345. std::vector<mpt::ustring> CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors()
  346. {
  347. // IMPORTANT:
  348. // Signing keys are *NOT* stored on the same server as openmpt.org or the updates themselves,
  349. // because otherwise, a single compromised server could allow for rogue updates.
  350. return {
  351. U_("https://update.openmpt.de/update/"),
  352. U_("https://demo-scene.de/openmpt/update/")
  353. };
  354. }
  355. mpt::ustring CUpdateCheck::GetDefaultAPIURL()
  356. {
  357. return U_("https://update.openmpt.org/api/v3/");
  358. }
  359. std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
  360. int32 CUpdateCheck::GetNumCurrentRunningInstances()
  361. {
  362. return s_InstanceCount.load();
  363. }
  364. bool CUpdateCheck::IsSuitableUpdateMoment()
  365. {
  366. const auto documents = theApp.GetOpenDocuments();
  367. return std::all_of(documents.begin(), documents.end(), [](auto doc) { return !doc->IsModified(); });
  368. }
  369. // Start update check
  370. void CUpdateCheck::StartUpdateCheckAsync(bool isAutoUpdate)
  371. {
  372. bool loadPersisted = false;
  373. if(isAutoUpdate)
  374. {
  375. if(!TrackerSettings::Instance().UpdateEnabled)
  376. {
  377. return;
  378. }
  379. if(!IsSuitableUpdateMoment())
  380. {
  381. return;
  382. }
  383. int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
  384. if(updateCheckPeriod < 0)
  385. {
  386. return;
  387. }
  388. // Do we actually need to run the update check right now?
  389. const time_t now = time(nullptr);
  390. const time_t lastCheck = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
  391. // 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).
  392. const double secsSinceLastCheck = difftime(now, lastCheck);
  393. if(secsSinceLastCheck > 0.0 && secsSinceLastCheck < updateCheckPeriod * 86400.0)
  394. {
  395. loadPersisted = true;
  396. }
  397. // Never ran update checks before, so we notify the user of automatic update checks.
  398. if(TrackerSettings::Instance().UpdateShowUpdateHint)
  399. {
  400. TrackerSettings::Instance().UpdateShowUpdateHint = false;
  401. const auto checkIntervalDays = TrackerSettings::Instance().UpdateIntervalDays.Get();
  402. CString msg = MPT_CFORMAT("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates {}. If you do not want this, you can disable update checks in the setup.")
  403. (
  404. checkIntervalDays == 0 ? CString(_T("on every program start")) :
  405. checkIntervalDays == 1 ? CString(_T("every day")) :
  406. MPT_CFORMAT("every {} days")(checkIntervalDays)
  407. );
  408. if(Reporting::Confirm(msg, _T("OpenMPT Update")) == cnfNo)
  409. {
  410. TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
  411. return;
  412. }
  413. }
  414. } else
  415. {
  416. if(!IsSuitableUpdateMoment())
  417. {
  418. Reporting::Notification(_T("Please save all modified modules before updating OpenMPT."), _T("OpenMPT Update"));
  419. return;
  420. }
  421. if(!TrackerSettings::Instance().UpdateEnabled)
  422. {
  423. if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Update")) != cnfYes)
  424. {
  425. return;
  426. }
  427. }
  428. }
  429. TrackerSettings::Instance().UpdateShowUpdateHint = false;
  430. // ask if user wants to contribute system statistics
  431. if(!TrackerSettings::Instance().UpdateStatisticsConsentAsked)
  432. {
  433. const auto enableStatistics = Reporting::Confirm(
  434. U_("Do you want to contribute to OpenMPT by providing system statistics?\r\n\r\n") +
  435. mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), U_("\n"), U_("\r\n")) + U_("\r\n\r\n") +
  436. MPT_UFORMAT("This option was previously {} on your system.\r\n")(TrackerSettings::Instance().UpdateStatistics ? U_("enabled") : U_("disabled")),
  437. false, !TrackerSettings::Instance().UpdateStatistics.Get());
  438. TrackerSettings::Instance().UpdateStatistics = (enableStatistics == ConfirmAnswer::cnfYes);
  439. TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
  440. }
  441. int32 expected = 0;
  442. if(!s_InstanceCount.compare_exchange_strong(expected, 1))
  443. {
  444. return;
  445. }
  446. CUpdateCheck::Context context;
  447. context.window = CMainFrame::GetMainFrame();
  448. context.msgStart = MPT_WM_APP_UPDATECHECK_START;
  449. context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
  450. context.msgCanceled = MPT_WM_APP_UPDATECHECK_CANCELED;
  451. context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
  452. context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
  453. context.autoUpdate = isAutoUpdate;
  454. context.loadPersisted = loadPersisted;
  455. context.statistics = GetStatisticsDataV3(CUpdateCheck::Settings());
  456. std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
  457. }
  458. CUpdateCheck::Settings::Settings()
  459. : periodDays(TrackerSettings::Instance().UpdateIntervalDays)
  460. , channel(static_cast<UpdateChannel>(TrackerSettings::Instance().UpdateChannel.Get()))
  461. , persistencePath(theApp.GetConfigPath())
  462. , apiURL(TrackerSettings::Instance().UpdateAPIURL)
  463. , sendStatistics(TrackerSettings::Instance().UpdateStatistics)
  464. , statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
  465. {
  466. }
  467. CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
  468. : settings(settings)
  469. , context(context)
  470. {
  471. return;
  472. }
  473. void CUpdateCheck::ThreadFunc::operator () ()
  474. {
  475. SetThreadPriority(GetCurrentThread(), context.autoUpdate ? THREAD_PRIORITY_BELOW_NORMAL : THREAD_PRIORITY_NORMAL);
  476. CheckForUpdate(settings, context);
  477. }
  478. std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
  479. {
  480. nlohmann::json j;
  481. j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
  482. j["OpenMPT"]["BuildVariant"] = BuildVariants().GetBuildVariantName(BuildVariants().GetBuildVariant());
  483. j["OpenMPT"]["Architecture"] = mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture());
  484. j["Update"]["PeriodDays"] = settings.periodDays;
  485. j["Update"]["Channel"] = ((settings.channel == UpdateChannelRelease) ? U_("Release") : (settings.channel == UpdateChannelNext) ? U_("Next") : (settings.channel == UpdateChannelDevelopment) ? U_("Development") : U_(""));
  486. j["System"]["Windows"]["Version"]["Name"] = mpt::OS::Windows::Version::GetName(mpt::OS::Windows::Version::Current());
  487. j["System"]["Windows"]["Version"]["Major"] = mpt::OS::Windows::Version::Current().GetSystem().Major;
  488. j["System"]["Windows"]["Version"]["Minor"] = mpt::OS::Windows::Version::Current().GetSystem().Minor;
  489. j["System"]["Windows"]["ServicePack"]["Major"] = mpt::OS::Windows::Version::Current().GetServicePack().Major;
  490. j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::OS::Windows::Version::Current().GetServicePack().Minor;
  491. j["System"]["Windows"]["Build"] = mpt::OS::Windows::Version::Current().GetBuild();
  492. j["System"]["Windows"]["Architecture"] = mpt::OS::Windows::Name(mpt::OS::Windows::GetHostArchitecture());
  493. j["System"]["Windows"]["IsWine"] = mpt::OS::Windows::IsWine();
  494. j["System"]["Windows"]["TypeRaw"] = MPT_AFORMAT("0x{}")(mpt::afmt::HEX0<8>(mpt::OS::Windows::Version::Current().GetTypeId()));
  495. std::vector<mpt::OS::Windows::Architecture> architectures = mpt::OS::Windows::GetSupportedProcessArchitectures(mpt::OS::Windows::GetHostArchitecture());
  496. for(const auto & arch : architectures)
  497. {
  498. j["System"]["Windows"]["ProcessArchitectures"][mpt::ToCharset(mpt::Charset::UTF8, mpt::OS::Windows::Name(arch))] = true;
  499. }
  500. j["System"]["Memory"] = mpt::OS::Windows::GetSystemMemorySize() / 1024 / 1024; // MB
  501. j["System"]["Threads"] = std::thread::hardware_concurrency();
  502. if(mpt::OS::Windows::IsWine())
  503. {
  504. mpt::OS::Wine::VersionContext v;
  505. j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
  506. if(v.Version().IsValid())
  507. {
  508. j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
  509. j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
  510. j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
  511. }
  512. j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
  513. }
  514. const SoundDevice::Identifier deviceIdentifier = TrackerSettings::Instance().GetSoundDeviceIdentifier();
  515. const SoundDevice::Info deviceInfo = theApp.GetSoundDevicesManager()->FindDeviceInfo(deviceIdentifier);
  516. const SoundDevice::Settings deviceSettings = TrackerSettings::Instance().GetSoundDeviceSettings(deviceIdentifier);
  517. j["OpenMPT"]["SoundDevice"]["Type"] = deviceInfo.type;
  518. j["OpenMPT"]["SoundDevice"]["Name"] = deviceInfo.name;
  519. j["OpenMPT"]["SoundDevice"]["Settings"]["Samplerate"] = deviceSettings.Samplerate;
  520. j["OpenMPT"]["SoundDevice"]["Settings"]["Latency"] = deviceSettings.Latency;
  521. j["OpenMPT"]["SoundDevice"]["Settings"]["UpdateInterval"] = deviceSettings.UpdateInterval;
  522. j["OpenMPT"]["SoundDevice"]["Settings"]["Channels"] = deviceSettings.Channels.GetNumHostChannels();
  523. j["OpenMPT"]["SoundDevice"]["Settings"]["BoostThreadPriority"] = deviceSettings.BoostThreadPriority;
  524. j["OpenMPT"]["SoundDevice"]["Settings"]["ExclusiveMode"] = deviceSettings.ExclusiveMode;
  525. j["OpenMPT"]["SoundDevice"]["Settings"]["UseHardwareTiming"] = deviceSettings.UseHardwareTiming;
  526. j["OpenMPT"]["SoundDevice"]["Settings"]["KeepDeviceRunning"] = deviceSettings.KeepDeviceRunning;
  527. #ifdef MPT_ENABLE_ARCH_INTRINSICS
  528. const CPU::Info CPUInfo = CPU::Info::Get();
  529. j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(CPUInfo.VendorID));
  530. j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(CPUInfo.BrandID));
  531. j["System"]["Processor"]["CpuidRaw"] = mpt::afmt::hex0<8>(CPUInfo.CPUID);
  532. j["System"]["Processor"]["Id"]["Family"] = CPUInfo.Family;
  533. j["System"]["Processor"]["Id"]["Model"] = CPUInfo.Model;
  534. j["System"]["Processor"]["Id"]["Stepping"] = CPUInfo.Stepping;
  535. j["System"]["Processor"]["Features"]["lm"] = ((CPUInfo.AvailableFeatures & CPU::feature::lm) != 0);
  536. j["System"]["Processor"]["Features"]["mmx"] = ((CPUInfo.AvailableFeatures & CPU::feature::mmx) != 0);
  537. j["System"]["Processor"]["Features"]["sse"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse) != 0);
  538. j["System"]["Processor"]["Features"]["sse2"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse2) != 0);
  539. j["System"]["Processor"]["Features"]["sse3"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse3) != 0);
  540. j["System"]["Processor"]["Features"]["ssse3"] = ((CPUInfo.AvailableFeatures & CPU::feature::ssse3) != 0);
  541. j["System"]["Processor"]["Features"]["sse4.1"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse4_1) != 0);
  542. j["System"]["Processor"]["Features"]["sse4.2"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse4_2) != 0);
  543. j["System"]["Processor"]["Features"]["avx"] = ((CPUInfo.AvailableFeatures & CPU::feature::avx) != 0);
  544. j["System"]["Processor"]["Features"]["avx2"] = ((CPUInfo.AvailableFeatures & CPU::feature::avx2) != 0);
  545. #endif // MPT_ENABLE_ARCH_INTRINSICS
  546. return j.dump(1, '\t');
  547. }
  548. // Run update check (independent thread)
  549. UpdateCheckResult CUpdateCheck::SearchUpdate(const CUpdateCheck::Context &context, const CUpdateCheck::Settings &settings, const std::string &statistics)
  550. {
  551. UpdateCheckResult result;
  552. {
  553. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 0))
  554. {
  555. throw CUpdateCheck::Cancel();
  556. }
  557. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 10))
  558. {
  559. throw CUpdateCheck::Cancel();
  560. }
  561. bool loaded = false;
  562. // try to load cached results before establishing any connection
  563. if(context.loadPersisted)
  564. {
  565. try
  566. {
  567. InputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"));
  568. if(f.IsValid())
  569. {
  570. std::vector<std::byte> data = GetFileReader(f).ReadRawDataAsByteVector();
  571. nlohmann::json::parse(mpt::buffer_cast<std::string>(data)).get<Update::versions>();
  572. result.CheckTime = time_t{};
  573. result.json = data;
  574. loaded = true;
  575. }
  576. } catch(mpt::out_of_memory e)
  577. {
  578. mpt::delete_out_of_memory(e);
  579. } catch(const std::exception &)
  580. {
  581. // ignore
  582. }
  583. }
  584. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 20))
  585. {
  586. throw CUpdateCheck::Cancel();
  587. }
  588. if(!loaded)
  589. {
  590. HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
  591. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 30))
  592. {
  593. throw CUpdateCheck::Cancel();
  594. }
  595. result = SearchUpdateModern(internet, settings);
  596. try
  597. {
  598. mpt::SafeOutputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"), std::ios::binary);
  599. f.stream().imbue(std::locale::classic());
  600. mpt::IO::WriteRaw(f.stream(), mpt::as_span(result.json));
  601. f.stream().flush();
  602. } catch(mpt::out_of_memory e)
  603. {
  604. mpt::delete_out_of_memory(e);
  605. } catch(const std::exception &)
  606. {
  607. // ignore
  608. }
  609. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 50))
  610. {
  611. throw CUpdateCheck::Cancel();
  612. }
  613. SendStatistics(internet, settings, statistics);
  614. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 70))
  615. {
  616. throw CUpdateCheck::Cancel();
  617. }
  618. }
  619. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 90))
  620. {
  621. throw CUpdateCheck::Cancel();
  622. }
  623. CleanOldUpdates(settings, context);
  624. if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 100))
  625. {
  626. throw CUpdateCheck::Cancel();
  627. }
  628. }
  629. return result;
  630. }
  631. void CUpdateCheck::CleanOldUpdates(const CUpdateCheck::Settings & /* settings */ , const CUpdateCheck::Context & /* context */ )
  632. {
  633. mpt::PathString dirTemp = mpt::GetTempDirectory();
  634. if(dirTemp.empty())
  635. {
  636. return;
  637. }
  638. if(PathIsRelative(dirTemp.AsNative().c_str()))
  639. {
  640. return;
  641. }
  642. if(!dirTemp.IsDirectory())
  643. {
  644. return;
  645. }
  646. mpt::PathString dirTempOpenMPT = dirTemp + P_("OpenMPT") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
  647. mpt::PathString dirTempOpenMPTUpdates = dirTempOpenMPT + P_("Updates") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
  648. mpt::DeleteWholeDirectoryTree(dirTempOpenMPTUpdates);
  649. }
  650. void CUpdateCheck::SendStatistics(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings, const std::string &statistics)
  651. {
  652. if(settings.sendStatistics)
  653. {
  654. HTTP::Request requestStatistics;
  655. if(settings.statisticsUUID.IsValid())
  656. {
  657. requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID)));
  658. requestStatistics.method = HTTP::Method::Put;
  659. } else
  660. {
  661. requestStatistics.SetURI(ParseURI(settings.apiURL + U_("statistics/")));
  662. requestStatistics.method = HTTP::Method::Post;
  663. }
  664. requestStatistics.dataMimeType = HTTP::MimeType::JSON();
  665. requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
  666. std::string jsondata = statistics;
  667. MPT_LOG_GLOBAL(LogInformation, "Update", mpt::ToUnicode(mpt::Charset::UTF8, jsondata));
  668. requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
  669. #if defined(MPT_BUILD_RETRO)
  670. requestSatistics.InsecureTLSDowngradeWindowsXP();
  671. #endif // MPT_BUILD_RETRO
  672. internet(requestStatistics);
  673. }
  674. }
  675. UpdateCheckResult CUpdateCheck::SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
  676. {
  677. HTTP::Request request;
  678. request.SetURI(ParseURI(settings.apiURL + MPT_UFORMAT("update/{}")(GetChannelName(static_cast<UpdateChannel>(settings.channel)))));
  679. request.method = HTTP::Method::Get;
  680. request.acceptMimeTypes = HTTP::MimeTypes::JSON();
  681. request.flags = HTTP::NoCache;
  682. #if defined(MPT_BUILD_RETRO)
  683. request.InsecureTLSDowngradeWindowsXP();
  684. #endif // MPT_BUILD_RETRO
  685. HTTP::Result resultHTTP = internet(request);
  686. // Retrieve HTTP status code.
  687. if(resultHTTP.Status >= 400)
  688. {
  689. 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));
  690. }
  691. // Now, evaluate the downloaded data.
  692. UpdateCheckResult result;
  693. result.CheckTime = time(nullptr);
  694. try
  695. {
  696. nlohmann::json::parse(mpt::buffer_cast<std::string>(resultHTTP.Data)).get<Update::versions>();
  697. result.json = resultHTTP.Data;
  698. } catch(mpt::out_of_memory e)
  699. {
  700. mpt::rethrow_out_of_memory(e);
  701. } catch(const nlohmann::json::exception &e)
  702. {
  703. 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)));
  704. }
  705. return result;
  706. }
  707. void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
  708. {
  709. // incremented before starting the thread
  710. MPT_ASSERT(s_InstanceCount.load() >= 1);
  711. UpdateCheckResult result;
  712. try
  713. {
  714. context.window->SendMessage(context.msgStart, context.autoUpdate ? 1 : 0, 0);
  715. try
  716. {
  717. result = SearchUpdate(context, settings, context.statistics);
  718. } catch(const bad_uri &e)
  719. {
  720. throw CUpdateCheck::Error(MPT_CFORMAT("Error parsing update URL: {}")(mpt::get_exception_text<mpt::ustring>(e)));
  721. } catch(const HTTP::exception &e)
  722. {
  723. throw CUpdateCheck::Error(CString(_T("HTTP error: ")) + mpt::ToCString(e.GetMessage()));
  724. }
  725. } catch(const CUpdateCheck::Cancel &)
  726. {
  727. context.window->SendMessage(context.msgCanceled, context.autoUpdate ? 1 : 0, 0);
  728. s_InstanceCount.fetch_sub(1);
  729. MPT_ASSERT(s_InstanceCount.load() >= 0);
  730. return;
  731. } catch(const CUpdateCheck::Error &e)
  732. {
  733. context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
  734. s_InstanceCount.fetch_sub(1);
  735. MPT_ASSERT(s_InstanceCount.load() >= 0);
  736. return;
  737. }
  738. context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
  739. s_InstanceCount.fetch_sub(1);
  740. MPT_ASSERT(s_InstanceCount.load() >= 0);
  741. }
  742. bool CUpdateCheck::IsAutoUpdateFromMessage(WPARAM wparam, LPARAM /* lparam */ )
  743. {
  744. return wparam ? true : false;
  745. }
  746. const UpdateCheckResult &CUpdateCheck::MessageAsResult(WPARAM /* wparam */ , LPARAM lparam)
  747. {
  748. return *reinterpret_cast<UpdateCheckResult *>(lparam);
  749. }
  750. const CUpdateCheck::Error &CUpdateCheck::MessageAsError(WPARAM /* wparam */ , LPARAM lparam)
  751. {
  752. return *reinterpret_cast<CUpdateCheck::Error*>(lparam);
  753. }
  754. static const char * const updateScript = R"vbs(
  755. Wscript.Echo
  756. Wscript.Echo "OpenMPT portable Update"
  757. Wscript.Echo "======================="
  758. Wscript.Echo "[ 0%] Waiting for OpenMPT to close..."
  759. WScript.Sleep 2000
  760. Wscript.Echo "[ 10%] Loading update settings..."
  761. zip = WScript.Arguments.Item(0)
  762. subfolder = WScript.Arguments.Item(1)
  763. dst = WScript.Arguments.Item(2)
  764. restartbinary = WScript.Arguments.Item(3)
  765. Wscript.Echo "[ 20%] Preparing update..."
  766. Set fso = CreateObject("Scripting.FileSystemObject")
  767. Set shell = CreateObject("Wscript.Shell")
  768. Set application = CreateObject("Shell.Application")
  769. Sub CreateFolder(pathname)
  770. If Not fso.FolderExists(pathname) Then
  771. fso.CreateFolder pathname
  772. End If
  773. End Sub
  774. Sub DeleteFolder(pathname)
  775. If fso.FolderExists(pathname) Then
  776. fso.DeleteFolder pathname
  777. End If
  778. End Sub
  779. Sub UnZIP(zipfilename, destinationfolder)
  780. If Not fso.FolderExists(destinationfolder) Then
  781. fso.CreateFolder(destinationfolder)
  782. End If
  783. application.NameSpace(destinationfolder).Copyhere application.NameSpace(zipfilename).Items, 16+256
  784. End Sub
  785. Wscript.Echo "[ 30%] Changing to temporary directory..."
  786. shell.CurrentDirectory = fso.GetParentFolderName(WScript.ScriptFullName)
  787. Wscript.Echo "[ 40%] Decompressing update..."
  788. UnZIP zip, fso.BuildPath(fso.GetAbsolutePathName("."), "tmp")
  789. Wscript.Echo "[ 60%] Installing update..."
  790. If subfolder = "" Or subfolder = "." Then
  791. fso.CopyFolder fso.BuildPath(fso.GetAbsolutePathName("."), "tmp"), dst, True
  792. Else
  793. fso.CopyFolder fso.BuildPath(fso.BuildPath(fso.GetAbsolutePathName("."), "tmp"), subfolder), dst, True
  794. End If
  795. Wscript.Echo "[ 80%] Deleting temporary directory..."
  796. DeleteFolder fso.BuildPath(fso.GetAbsolutePathName("."), "tmp")
  797. Wscript.Echo "[ 90%] Restarting OpenMPT..."
  798. application.ShellExecute fso.BuildPath(dst, restartbinary), , dst, , 10
  799. Wscript.Echo "[100%] Update successful!"
  800. Wscript.Echo
  801. WScript.Sleep 1000
  802. Wscript.Echo "Closing update window in 5 seconds..."
  803. WScript.Sleep 1000
  804. Wscript.Echo "Closing update window in 4 seconds..."
  805. WScript.Sleep 1000
  806. Wscript.Echo "Closing update window in 3 seconds..."
  807. WScript.Sleep 1000
  808. Wscript.Echo "Closing update window in 2 seconds..."
  809. WScript.Sleep 1000
  810. Wscript.Echo "Closing update window in 1 seconds..."
  811. WScript.Sleep 1000
  812. Wscript.Echo "Closing update window..."
  813. WScript.Quit
  814. )vbs";
  815. class CDoUpdate: public CProgressDialog
  816. {
  817. private:
  818. Update::download download;
  819. class Aborted : public std::exception {};
  820. class Warning : public std::exception
  821. {
  822. private:
  823. mpt::ustring msg;
  824. public:
  825. Warning(const mpt::ustring &msg_)
  826. : msg(msg_)
  827. {
  828. return;
  829. }
  830. mpt::ustring get_msg() const
  831. {
  832. return msg;
  833. }
  834. };
  835. class Error : public std::exception
  836. {
  837. private:
  838. mpt::ustring msg;
  839. public:
  840. Error(const mpt::ustring &msg_)
  841. : msg(msg_)
  842. {
  843. return;
  844. }
  845. mpt::ustring get_msg() const
  846. {
  847. return msg;
  848. }
  849. };
  850. public:
  851. CDoUpdate(Update::download download, CWnd *parent = nullptr)
  852. : CProgressDialog(parent)
  853. , download(download)
  854. {
  855. return;
  856. }
  857. void UpdateProgress(const CString &text, double percent)
  858. {
  859. SetText(text);
  860. SetProgress(static_cast<uint64>(percent * 100.0));
  861. ProcessMessages();
  862. if(m_abort)
  863. {
  864. throw Aborted();
  865. }
  866. }
  867. void Run() override
  868. {
  869. try
  870. {
  871. SetTitle(_T("OpenMPT Update"));
  872. SetAbortText(_T("Cancel"));
  873. SetText(_T("OpenMPT Update"));
  874. SetRange(0, 10000);
  875. ProcessMessages();
  876. Update::downloadinfo downloadinfo;
  877. mpt::PathString dirTempOpenMPTUpdates;
  878. mpt::PathString updateFilename;
  879. {
  880. UpdateProgress(_T("Connecting..."), 0.0);
  881. HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
  882. UpdateProgress(_T("Downloading update information..."), 1.0);
  883. std::vector<std::byte> rawDownloadInfo;
  884. {
  885. HTTP::Request request;
  886. request.SetURI(ParseURI(download.url));
  887. request.method = HTTP::Method::Get;
  888. request.acceptMimeTypes = HTTP::MimeTypes::JSON();
  889. #if defined(MPT_BUILD_RETRO)
  890. request.InsecureTLSDowngradeWindowsXP();
  891. #endif // MPT_BUILD_RETRO
  892. HTTP::Result resultHTTP = internet(request);
  893. if(resultHTTP.Status != 200)
  894. {
  895. throw Error(MPT_UFORMAT("Error downloading update information: HTTP status {}.")(resultHTTP.Status));
  896. }
  897. rawDownloadInfo = std::move(resultHTTP.Data);
  898. }
  899. if(!TrackerSettings::Instance().UpdateSkipSignatureVerificationUNSECURE)
  900. {
  901. std::vector<std::byte> rawSignature;
  902. UpdateProgress(_T("Retrieving update signature..."), 2.0);
  903. {
  904. HTTP::Request request;
  905. request.SetURI(ParseURI(download.url + U_(".jws.json")));
  906. request.method = HTTP::Method::Get;
  907. request.acceptMimeTypes = HTTP::MimeTypes::JSON();
  908. #if defined(MPT_BUILD_RETRO)
  909. request.InsecureTLSDowngradeWindowsXP();
  910. #endif // MPT_BUILD_RETRO
  911. HTTP::Result resultHTTP = internet(request);
  912. if(resultHTTP.Status != 200)
  913. {
  914. throw Error(MPT_UFORMAT("Error downloading update signature: HTTP status {}.")(resultHTTP.Status));
  915. }
  916. rawSignature = std::move(resultHTTP.Data);
  917. }
  918. UpdateProgress(_T("Retrieving update signing public keys..."), 3.0);
  919. std::vector<mpt::crypto::asymmetric::rsassa_pss<>::public_key> keys;
  920. {
  921. std::vector<mpt::ustring> keyAnchors = TrackerSettings::Instance().UpdateSigningKeysRootAnchors;
  922. if(keyAnchors.empty())
  923. {
  924. Reporting::Warning(U_("Warning: No update signing public key root anchors configured. Update cannot be verified."), U_("OpenMPT Update"));
  925. }
  926. for(const auto & keyAnchor : keyAnchors)
  927. {
  928. HTTP::Request request;
  929. request.SetURI(ParseURI(keyAnchor + U_("signingkeys.jwkset.json")));
  930. request.method = HTTP::Method::Get;
  931. request.flags = HTTP::NoCache;
  932. request.acceptMimeTypes = HTTP::MimeTypes::JSON();
  933. try
  934. {
  935. #if defined(MPT_BUILD_RETRO)
  936. request.InsecureTLSDowngradeWindowsXP();
  937. #endif // MPT_BUILD_RETRO
  938. HTTP::Result resultHTTP = internet(request);
  939. resultHTTP.CheckStatus(200);
  940. mpt::append(keys, mpt::crypto::asymmetric::rsassa_pss<>::parse_jwk_set(mpt::ToUnicode(mpt::Charset::UTF8, mpt::buffer_cast<std::string>(resultHTTP.Data))));
  941. } catch(mpt::out_of_memory e)
  942. {
  943. mpt::rethrow_out_of_memory(e);
  944. } catch(const std::exception &e)
  945. {
  946. Reporting::Warning(MPT_UFORMAT("Warning: Retrieving update signing public keys from {} failed: {}")(keyAnchor, mpt::get_exception_text<mpt::ustring>(e)), U_("OpenMPT Update"));
  947. } catch(...)
  948. {
  949. Reporting::Warning(MPT_UFORMAT("Warning: Retrieving update signing public keys from {} failed.")(keyAnchor), U_("OpenMPT Update"));
  950. }
  951. }
  952. }
  953. if(keys.empty())
  954. {
  955. throw Error(U_("Error retrieving update signing public keys."));
  956. }
  957. UpdateProgress(_T("Verifying signature..."), 4.0);
  958. std::vector<std::byte> expectedPayload = mpt::buffer_cast<std::vector<std::byte>>(rawDownloadInfo);
  959. mpt::ustring signature = mpt::ToUnicode(mpt::Charset::UTF8, mpt::buffer_cast<std::string>(rawSignature));
  960. mpt::crypto::asymmetric::rsassa_pss<>::jws_verify_at_least_one(keys, expectedPayload, signature);
  961. }
  962. UpdateProgress(_T("Parsing update information..."), 5.0);
  963. try
  964. {
  965. downloadinfo = nlohmann::json::parse(mpt::buffer_cast<std::string>(rawDownloadInfo)).get<Update::downloadinfo>();
  966. } catch(const nlohmann::json::exception &e)
  967. {
  968. throw Error(MPT_UFORMAT("Error parsing update information: {}.")(mpt::get_exception_text<mpt::ustring>(e)));
  969. }
  970. UpdateProgress(_T("Preparing download..."), 6.0);
  971. mpt::PathString dirTemp = mpt::GetTempDirectory();
  972. mpt::PathString dirTempOpenMPT = dirTemp + P_("OpenMPT") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
  973. dirTempOpenMPTUpdates = dirTempOpenMPT + P_("Updates") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
  974. updateFilename = dirTempOpenMPTUpdates + mpt::PathString::FromUnicode(downloadinfo.filename);
  975. ::CreateDirectory(dirTempOpenMPT.AsNativePrefixed().c_str(), NULL);
  976. ::CreateDirectory(dirTempOpenMPTUpdates.AsNativePrefixed().c_str(), NULL);
  977. {
  978. UpdateProgress(_T("Creating file..."), 7.0);
  979. mpt::SafeOutputFile file(updateFilename, std::ios::binary);
  980. file.stream().imbue(std::locale::classic());
  981. file.stream().exceptions(std::ios::failbit | std::ios::badbit);
  982. UpdateProgress(_T("Downloading update..."), 8.0);
  983. HTTP::Request request;
  984. request.SetURI(ParseURI(downloadinfo.url));
  985. request.method = HTTP::Method::Get;
  986. request.acceptMimeTypes = HTTP::MimeTypes::Binary();
  987. request.outputStream = &file.stream();
  988. request.progressCallback = [&](HTTP::Progress progress, uint64 transferred, std::optional<uint64> expectedSize) {
  989. switch(progress)
  990. {
  991. case HTTP::Progress::Start:
  992. SetProgress(900);
  993. break;
  994. case HTTP::Progress::ConnectionEstablished:
  995. SetProgress(1000);
  996. break;
  997. case HTTP::Progress::RequestOpened:
  998. SetProgress(1100);
  999. break;
  1000. case HTTP::Progress::RequestSent:
  1001. SetProgress(1200);
  1002. break;
  1003. case HTTP::Progress::ResponseReceived:
  1004. SetProgress(1300);
  1005. break;
  1006. case HTTP::Progress::TransferBegin:
  1007. SetProgress(1400);
  1008. break;
  1009. case HTTP::Progress::TransferRunning:
  1010. if(expectedSize && ((*expectedSize) != 0))
  1011. {
  1012. SetProgress(static_cast<int64>((static_cast<double>(transferred) / static_cast<double>(*expectedSize)) * (10000.0-1500.0-400.0) + 1500.0));
  1013. } else
  1014. {
  1015. SetProgress((1500 + 9600) / 2);
  1016. }
  1017. break;
  1018. case HTTP::Progress::TransferDone:
  1019. SetProgress(9600);
  1020. break;
  1021. }
  1022. ProcessMessages();
  1023. if(m_abort)
  1024. {
  1025. throw HTTP::Abort();
  1026. }
  1027. };
  1028. #if defined(MPT_BUILD_RETRO)
  1029. request.InsecureTLSDowngradeWindowsXP();
  1030. #endif // MPT_BUILD_RETRO
  1031. HTTP::Result resultHTTP = internet(request);
  1032. if(resultHTTP.Status != 200)
  1033. {
  1034. throw Error(MPT_UFORMAT("Error downloading update: HTTP status {}.")(resultHTTP.Status));
  1035. }
  1036. }
  1037. UpdateProgress(_T("Disconnecting..."), 97.0);
  1038. }
  1039. UpdateProgress(_T("Verifying download..."), 98.0);
  1040. bool verified = false;
  1041. for(const auto & [algorithm, value] : downloadinfo.checksums)
  1042. {
  1043. if(algorithm == U_("SHA-512"))
  1044. {
  1045. std::vector<std::byte> binhash = mpt::decode_hex(value);
  1046. if(binhash.size() != 512/8)
  1047. {
  1048. throw Error(U_("Download verification failed."));
  1049. }
  1050. std::array<std::byte, 512/8> expected;
  1051. std::copy(binhash.begin(), binhash.end(), expected.begin());
  1052. mpt::crypto::hash::SHA512 hash;
  1053. mpt::ifstream f(updateFilename, std::ios::binary);
  1054. f.imbue(std::locale::classic());
  1055. f.exceptions(std::ios::badbit);
  1056. while(!mpt::IO::IsEof(f))
  1057. {
  1058. std::array<std::byte, mpt::IO::BUFFERSIZE_TINY> buf;
  1059. hash.process(mpt::IO::ReadRaw(f, mpt::as_span(buf)));
  1060. }
  1061. std::array<std::byte, 512/8> gotten = hash.result();
  1062. if(gotten != expected)
  1063. {
  1064. throw Error(U_("Download verification failed."));
  1065. }
  1066. verified = true;
  1067. }
  1068. }
  1069. if(!verified)
  1070. {
  1071. throw Error(U_("Error verifying update: No suitable checksum found."));
  1072. }
  1073. UpdateProgress(_T("Installing update..."), 99.0);
  1074. bool wantClose = false;
  1075. if(download.can_autoupdate && (Version::Current() >= Version::Parse(download.autoupdate_minversion)))
  1076. {
  1077. if(download.type == U_("installer") && downloadinfo.autoupdate_installer)
  1078. {
  1079. if(theApp.IsSourceTreeMode())
  1080. {
  1081. throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(updateFilename, mpt::String::Combine(downloadinfo.autoupdate_installer->arguments, U_(" "))));
  1082. }
  1083. if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
  1084. updateFilename.AsNative().c_str(),
  1085. mpt::ToWin(mpt::String::Combine(downloadinfo.autoupdate_installer->arguments, U_(" "))).c_str(),
  1086. dirTempOpenMPTUpdates.AsNative().c_str(),
  1087. SW_SHOWDEFAULT)) < 32)
  1088. {
  1089. throw Error(U_("Error launching update."));
  1090. }
  1091. } else if(download.type == U_("archive") && downloadinfo.autoupdate_archive)
  1092. {
  1093. try
  1094. {
  1095. mpt::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
  1096. file.stream().imbue(std::locale::classic());
  1097. file.stream().exceptions(std::ios::failbit | std::ios::badbit);
  1098. mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript)));
  1099. } catch(...)
  1100. {
  1101. throw Error(U_("Error creating update script."));
  1102. }
  1103. std::vector<mpt::ustring> arguments;
  1104. arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.vbs")).ToUnicode() + U_("\""));
  1105. arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
  1106. arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
  1107. arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
  1108. arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
  1109. if(theApp.IsSourceTreeMode())
  1110. {
  1111. throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::String::Combine(arguments, U_(" "))));
  1112. }
  1113. if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
  1114. P_("cscript.exe").AsNative().c_str(),
  1115. mpt::ToWin(mpt::String::Combine(arguments, U_(" "))).c_str(),
  1116. dirTempOpenMPTUpdates.AsNative().c_str(),
  1117. SW_SHOWDEFAULT)) < 32)
  1118. {
  1119. throw Error(U_("Error launching update."));
  1120. }
  1121. wantClose = true;
  1122. } else
  1123. {
  1124. CTrackApp::OpenDirectory(dirTempOpenMPTUpdates);
  1125. wantClose = true;
  1126. }
  1127. } else
  1128. {
  1129. CTrackApp::OpenDirectory(dirTempOpenMPTUpdates);
  1130. wantClose = true;
  1131. }
  1132. UpdateProgress(_T("Waiting for installer..."), 100.0);
  1133. if(wantClose)
  1134. {
  1135. CMainFrame::GetMainFrame()->PostMessage(WM_QUIT, 0, 0);
  1136. }
  1137. EndDialog(IDOK);
  1138. } catch(mpt::out_of_memory e)
  1139. {
  1140. mpt::delete_out_of_memory(e);
  1141. Reporting::Error(U_("Not enough memory to install update."), U_("OpenMPT Update Error"));
  1142. EndDialog(IDCANCEL);
  1143. return;
  1144. } catch(const HTTP::Abort &)
  1145. {
  1146. EndDialog(IDCANCEL);
  1147. return;
  1148. } catch(const Aborted &)
  1149. {
  1150. EndDialog(IDCANCEL);
  1151. return;
  1152. } catch(const Warning &e)
  1153. {
  1154. Reporting::Warning(e.get_msg(), U_("OpenMPT Update"));
  1155. EndDialog(IDCANCEL);
  1156. return;
  1157. } catch(const Error &e)
  1158. {
  1159. Reporting::Error(e.get_msg(), U_("OpenMPT Update Error"));
  1160. EndDialog(IDCANCEL);
  1161. return;
  1162. } catch(const std::exception &e)
  1163. {
  1164. Reporting::Error(MPT_UFORMAT("Error installing update: {}")(mpt::get_exception_text<mpt::ustring>(e)), U_("OpenMPT Update Error"));
  1165. EndDialog(IDCANCEL);
  1166. return;
  1167. } catch(...)
  1168. {
  1169. Reporting::Error(U_("Error installing update."), U_("OpenMPT Update Error"));
  1170. EndDialog(IDCANCEL);
  1171. return;
  1172. }
  1173. }
  1174. };
  1175. void CUpdateCheck::AcknowledgeSuccess(const UpdateCheckResult &result)
  1176. {
  1177. if(!result.IsFromCache())
  1178. {
  1179. TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(result.CheckTime);
  1180. }
  1181. }
  1182. void CUpdateCheck::ShowSuccessGUI(const bool autoUpdate, const UpdateCheckResult &result)
  1183. {
  1184. bool modal = !autoUpdate;
  1185. Update::versions updateData = nlohmann::json::parse(mpt::buffer_cast<std::string>(result.json)).get<Update::versions>();
  1186. UpdateInfo updateInfo = GetBestDownload(updateData);
  1187. if(!updateInfo.IsAvailable())
  1188. {
  1189. if(modal)
  1190. {
  1191. Reporting::Information(U_("You already have the latest version of OpenMPT installed."), U_("OpenMPT Update"));
  1192. }
  1193. return;
  1194. }
  1195. auto &versionInfo = updateData[updateInfo.version];
  1196. if(autoUpdate && (mpt::ToCString(versionInfo.version) == TrackerSettings::Instance().UpdateIgnoreVersion))
  1197. {
  1198. return;
  1199. }
  1200. if(autoUpdate && TrackerSettings::Instance().UpdateInstallAutomatically && !updateInfo.download.empty() && versionInfo.downloads[updateInfo.download].can_autoupdate && (Version::Current() >= Version::Parse(versionInfo.downloads[updateInfo.download].autoupdate_minversion)))
  1201. {
  1202. CDoUpdate updateDlg(versionInfo.downloads[updateInfo.download], theApp.GetMainWnd());
  1203. if(updateDlg.DoModal() != IDOK)
  1204. {
  1205. return;
  1206. }
  1207. } else
  1208. {
  1209. const TCHAR *action = _T("&View Announcement");
  1210. const bool canInstall = mpt::OS::Windows::IsOriginal() && !updateInfo.download.empty() && versionInfo.downloads[updateInfo.download].can_autoupdate && (Version::Current() >= Version::Parse(versionInfo.downloads[updateInfo.download].autoupdate_minversion));
  1211. const bool canDownload = !canInstall && !updateInfo.download.empty() && !versionInfo.downloads[updateInfo.download].download_url.empty();
  1212. if(canInstall)
  1213. {
  1214. action = _T("&Install Now");
  1215. } else if(canDownload)
  1216. {
  1217. action = _T("&Download Now");
  1218. }
  1219. // always show indicator, do not highlight it with a tooltip if we show a modal window later or when it is a cached result
  1220. if(!CMainFrame::GetMainFrame()->ShowUpdateIndicator(result, mpt::ToCString(versionInfo.version), mpt::ToCString(versionInfo.announcement_url), !modal && !result.IsFromCache()))
  1221. {
  1222. // on failure to show indicator, continue and show modal dialog
  1223. modal = true;
  1224. }
  1225. if(!modal)
  1226. {
  1227. return;
  1228. }
  1229. UpdateDialog dlg(
  1230. mpt::ToCString(versionInfo.version),
  1231. mpt::ToCString(versionInfo.date),
  1232. mpt::ToCString(versionInfo.announcement_url),
  1233. action);
  1234. if(dlg.DoModal() != IDOK)
  1235. {
  1236. return;
  1237. }
  1238. if(canInstall)
  1239. {
  1240. CDoUpdate updateDlg(versionInfo.downloads[updateInfo.download], theApp.GetMainWnd());
  1241. if(updateDlg.DoModal() != IDOK)
  1242. {
  1243. return;
  1244. }
  1245. } else if(canDownload)
  1246. {
  1247. CTrackApp::OpenURL(versionInfo.downloads[updateInfo.download].download_url);
  1248. } else
  1249. {
  1250. CTrackApp::OpenURL(versionInfo.announcement_url);
  1251. }
  1252. }
  1253. }
  1254. void CUpdateCheck::ShowFailureGUI(const bool autoUpdate, const CUpdateCheck::Error &error)
  1255. {
  1256. if(!autoUpdate)
  1257. {
  1258. Reporting::Error(error.GetMessage(), _T("OpenMPT Update Error"));
  1259. }
  1260. }
  1261. CUpdateCheck::Error::Error(CString errorMessage)
  1262. : std::runtime_error(mpt::ToCharset(mpt::CharsetException, errorMessage))
  1263. , m_Message(errorMessage)
  1264. {
  1265. return;
  1266. }
  1267. CUpdateCheck::Error::Error(CString errorMessage, DWORD errorCode)
  1268. : std::runtime_error(mpt::ToCharset(mpt::CharsetException, FormatErrorCode(errorMessage, errorCode)))
  1269. , m_Message(errorMessage)
  1270. {
  1271. return;
  1272. }
  1273. CString CUpdateCheck::Error::GetMessage() const
  1274. {
  1275. return m_Message;
  1276. }
  1277. CString CUpdateCheck::Error::FormatErrorCode(CString errorMessage, DWORD errorCode)
  1278. {
  1279. errorMessage += mpt::ToCString(mpt::windows::GetErrorMessage(errorCode, GetModuleHandle(TEXT("wininet.dll"))));
  1280. return errorMessage;
  1281. }
  1282. CUpdateCheck::Cancel::Cancel()
  1283. {
  1284. return;
  1285. }
  1286. /////////////////////////////////////////////////////////////
  1287. // CUpdateSetupDlg
  1288. BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
  1289. ON_COMMAND(IDC_CHECK_UPDATEENABLED, &CUpdateSetupDlg::OnSettingsChanged)
  1290. ON_COMMAND(IDC_RADIO1, &CUpdateSetupDlg::OnSettingsChanged)
  1291. ON_COMMAND(IDC_RADIO2, &CUpdateSetupDlg::OnSettingsChanged)
  1292. ON_COMMAND(IDC_RADIO3, &CUpdateSetupDlg::OnSettingsChanged)
  1293. ON_COMMAND(IDC_BUTTON1, &CUpdateSetupDlg::OnCheckNow)
  1294. ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
  1295. ON_COMMAND(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY, &CUpdateSetupDlg::OnSettingsChanged)
  1296. ON_COMMAND(IDC_CHECK1, &CUpdateSetupDlg::OnSettingsChanged)
  1297. ON_NOTIFY(NM_CLICK, IDC_SYSLINK1, &CUpdateSetupDlg::OnShowStatisticsData)
  1298. END_MESSAGE_MAP()
  1299. CUpdateSetupDlg::CUpdateSetupDlg()
  1300. : CPropertyPage(IDD_OPTIONS_UPDATE)
  1301. , m_SettingChangedNotifyGuard(theApp.GetSettings(), TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
  1302. {
  1303. return;
  1304. }
  1305. void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
  1306. {
  1307. CDialog::DoDataExchange(pDX);
  1308. DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
  1309. }
  1310. BOOL CUpdateSetupDlg::OnInitDialog()
  1311. {
  1312. CPropertyPage::OnInitDialog();
  1313. CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
  1314. int radioID = 0;
  1315. uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
  1316. if(updateChannel == UpdateChannelRelease)
  1317. {
  1318. radioID = IDC_RADIO1;
  1319. } else if(updateChannel == UpdateChannelNext)
  1320. {
  1321. radioID = IDC_RADIO2;
  1322. } else if(updateChannel == UpdateChannelDevelopment)
  1323. {
  1324. radioID = IDC_RADIO3;
  1325. } else
  1326. {
  1327. radioID = IDC_RADIO1;
  1328. }
  1329. CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
  1330. int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
  1331. int ndx;
  1332. ndx = m_CbnUpdateFrequency.AddString(_T("always"));
  1333. m_CbnUpdateFrequency.SetItemData(ndx, 0);
  1334. if(periodDays >= 0)
  1335. {
  1336. m_CbnUpdateFrequency.SetCurSel(ndx);
  1337. }
  1338. ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
  1339. m_CbnUpdateFrequency.SetItemData(ndx, 1);
  1340. if(periodDays >= 1)
  1341. {
  1342. m_CbnUpdateFrequency.SetCurSel(ndx);
  1343. }
  1344. ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
  1345. m_CbnUpdateFrequency.SetItemData(ndx, 7);
  1346. if(periodDays >= 7)
  1347. {
  1348. m_CbnUpdateFrequency.SetCurSel(ndx);
  1349. }
  1350. ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
  1351. m_CbnUpdateFrequency.SetItemData(ndx, 30);
  1352. if(periodDays >= 30)
  1353. {
  1354. m_CbnUpdateFrequency.SetCurSel(ndx);
  1355. }
  1356. ndx = m_CbnUpdateFrequency.AddString(_T("never"));
  1357. m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
  1358. if(periodDays < 0)
  1359. {
  1360. m_CbnUpdateFrequency.SetCurSel(ndx);
  1361. }
  1362. CheckDlgButton(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY, TrackerSettings::Instance().UpdateInstallAutomatically ? BST_CHECKED : BST_UNCHECKED);
  1363. CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateStatistics ? BST_CHECKED : BST_UNCHECKED);
  1364. GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(mpt::ToCString(CUpdateCheck::GetStatisticsUserInformation(true)));
  1365. EnableDisableDialog();
  1366. m_SettingChangedNotifyGuard.Register(this);
  1367. SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
  1368. return TRUE;
  1369. }
  1370. void CUpdateSetupDlg::OnShowStatisticsData(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
  1371. {
  1372. CUpdateCheck::Settings settings;
  1373. uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
  1374. const int channelRadio = GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3);
  1375. if(channelRadio == IDC_RADIO1) updateChannel = UpdateChannelRelease;
  1376. if(channelRadio == IDC_RADIO2) updateChannel = UpdateChannelNext;
  1377. if(channelRadio == IDC_RADIO3) updateChannel = UpdateChannelDevelopment;
  1378. int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
  1379. settings.periodDays = updateCheckPeriod;
  1380. settings.channel = static_cast<UpdateChannel>(updateChannel);
  1381. settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
  1382. mpt::ustring statistics;
  1383. statistics += U_("Update:") + UL_("\n");
  1384. statistics += UL_("\n");
  1385. {
  1386. statistics += U_("GET ") + settings.apiURL + MPT_UFORMAT("update/{}")(GetChannelName(static_cast<UpdateChannel>(settings.channel))) + UL_("\n");
  1387. statistics += UL_("\n");
  1388. std::vector<mpt::ustring> keyAnchors = TrackerSettings::Instance().UpdateSigningKeysRootAnchors;
  1389. for(const auto & keyAnchor : keyAnchors)
  1390. {
  1391. statistics += U_("GET ") + keyAnchor + U_("signingkeys.jwkset.json") + UL_("\n");
  1392. statistics += UL_("\n");
  1393. }
  1394. }
  1395. if(settings.sendStatistics)
  1396. {
  1397. statistics += U_("Statistics:") + UL_("\n");
  1398. statistics += UL_("\n");
  1399. if(settings.statisticsUUID.IsValid())
  1400. {
  1401. statistics += U_("PUT ") + settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID) + UL_("\n");
  1402. } else
  1403. {
  1404. statistics += U_("POST ") + settings.apiURL + U_("statistics/") + UL_("\n");
  1405. }
  1406. statistics += mpt::String::Replace(mpt::ToUnicode(mpt::Charset::UTF8, CUpdateCheck::GetStatisticsDataV3(settings)), U_("\t"), U_(" "));
  1407. }
  1408. InfoDialog dlg(this);
  1409. dlg.SetCaption(_T("Update Statistics Data"));
  1410. dlg.SetContent(mpt::ToWin(mpt::String::Replace(statistics, U_("\n"), U_("\r\n"))));
  1411. dlg.DoModal();
  1412. }
  1413. void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
  1414. {
  1415. if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
  1416. {
  1417. CString updateText;
  1418. const time_t t = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
  1419. if(t > 0)
  1420. {
  1421. const tm* const lastUpdate = localtime(&t);
  1422. if(lastUpdate != nullptr)
  1423. {
  1424. updateText.Format(_T("The last successful update check was run on %04d-%02d-%02d, %02d:%02d."), lastUpdate->tm_year + 1900, lastUpdate->tm_mon + 1, lastUpdate->tm_mday, lastUpdate->tm_hour, lastUpdate->tm_min);
  1425. }
  1426. }
  1427. updateText += _T("\r\n");
  1428. SetDlgItemText(IDC_LASTUPDATE, updateText);
  1429. }
  1430. }
  1431. void CUpdateSetupDlg::EnableDisableDialog()
  1432. {
  1433. BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
  1434. GetDlgItem(IDC_STATIC_UDATECHANNEL)->EnableWindow(status);
  1435. GetDlgItem(IDC_RADIO1)->EnableWindow(status);
  1436. GetDlgItem(IDC_RADIO2)->EnableWindow(status);
  1437. GetDlgItem(IDC_RADIO3)->EnableWindow(status);
  1438. GetDlgItem(IDC_STATIC_UPDATECHECK)->EnableWindow(status);
  1439. GetDlgItem(IDC_STATIC_UPDATEFREQUENCY)->EnableWindow(status);
  1440. GetDlgItem(IDC_COMBO_UPDATEFREQUENCY)->EnableWindow(status);
  1441. GetDlgItem(IDC_BUTTON1)->EnableWindow(status);
  1442. GetDlgItem(IDC_LASTUPDATE)->EnableWindow(status);
  1443. GetDlgItem(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY)->EnableWindow(status);
  1444. GetDlgItem(IDC_STATIC_UPDATEPRIVACY)->EnableWindow(status);
  1445. GetDlgItem(IDC_CHECK1)->EnableWindow(status);
  1446. GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
  1447. GetDlgItem(IDC_SYSLINK1)->EnableWindow(status);
  1448. }
  1449. void CUpdateSetupDlg::OnSettingsChanged()
  1450. {
  1451. EnableDisableDialog();
  1452. SetModified(TRUE);
  1453. }
  1454. void CUpdateSetupDlg::OnOK()
  1455. {
  1456. uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
  1457. const int channelRadio = GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3);
  1458. if(channelRadio == IDC_RADIO1) updateChannel = UpdateChannelRelease;
  1459. if(channelRadio == IDC_RADIO2) updateChannel = UpdateChannelNext;
  1460. if(channelRadio == IDC_RADIO3) updateChannel = UpdateChannelDevelopment;
  1461. int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
  1462. TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
  1463. TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
  1464. TrackerSettings::Instance().UpdateInstallAutomatically = (IsDlgButtonChecked(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY) != BST_UNCHECKED);
  1465. TrackerSettings::Instance().UpdateChannel = updateChannel;
  1466. TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
  1467. CPropertyPage::OnOK();
  1468. }
  1469. BOOL CUpdateSetupDlg::OnSetActive()
  1470. {
  1471. CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_UPDATE;
  1472. return CPropertyPage::OnSetActive();
  1473. }
  1474. void CUpdateSetupDlg::OnCheckNow()
  1475. {
  1476. CUpdateCheck::DoManualUpdateCheck();
  1477. }
  1478. #endif // MPT_ENABLE_UPDATE
  1479. OPENMPT_NAMESPACE_END