using Avalonia.Threading; using Mesen.Config; using Mesen.Interop; using Mesen.Utilities; using Mesen.Windows; using CommunityToolkit.Mvvm.ComponentModel; using System; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; using System.Threading.Tasks; namespace Mesen.ViewModels { public partial class UpdatePromptViewModel : ViewModelBase { public Version LatestVersion { get; } public Version InstalledVersion { get; } public string Changelog { get; } [ObservableProperty] public partial bool IsUpdating { get; internal set; } [ObservableProperty] public partial int Progress { get; internal set; } public UpdateFileInfo? FileInfo => _fileInfo; private UpdateFileInfo? _fileInfo; public UpdatePromptViewModel(UpdateInfo updateInfo, UpdateFileInfo? file) { LatestVersion = updateInfo.LatestVersion; Changelog = updateInfo.ReleaseNotes; InstalledVersion = EmuApi.GetMesenVersion(); _fileInfo = file; } public static async Task GetUpdateInformation(bool silent) { UpdateInfo? updateInfo = null; try { using(var client = new HttpClient()) { string updateData = await client.GetStringAsync("https://raw.githubusercontent.com/nesdev-org/MesenAutoUpdate/refs/heads/main/latestversion.json"); updateInfo = (UpdateInfo?)JsonSerializer.Deserialize(updateData, typeof(UpdateInfo), MesenSerializerContext.Default); if( updateInfo == null || updateInfo.Files == null || updateInfo.Files.Where(f => f.DownloadUrl == null || (!f.DownloadUrl.StartsWith("https://github.com/nesdev-org/") && !f.DownloadUrl.StartsWith("https://github.com/SourMesen/"))).Count() > 0 ) { return null; } } } catch(Exception ex) { if(!silent) { Dispatcher.UIThread.Post(() => { MesenMsgBox.ShowException(ex); }); } } if(updateInfo != null) { string platform; if(OperatingSystem.IsWindows()) { if(OperatingSystem.IsWindowsVersionAtLeast(10)) { platform = "win"; } else { platform = "win7"; } } else if(OperatingSystem.IsLinux()) { platform = "linux"; } else if(OperatingSystem.IsMacOS()) { platform = "macos"; } else { return null; } platform += "-" + RuntimeInformation.OSArchitecture.ToString().ToLower(); platform += RuntimeFeature.IsDynamicCodeSupported ? "-jit" : "-aot"; if(OperatingSystem.IsLinux() && Program.ExePath.ToLower().EndsWith("appimage")) { platform += "-appimage"; } UpdateFileInfo? file = updateInfo.Files.Where(f => f.Platform.Contains(platform)).FirstOrDefault(); return updateInfo != null ? new UpdatePromptViewModel(updateInfo, file) : null; } return null; } public async Task UpdateMesen(UpdatePromptWindow wnd) { if(_fileInfo == null) { return false; } string downloadPath = Path.Combine(ConfigManager.BackupFolder, "Mesen." + LatestVersion.ToString(3)); using(var client = new HttpClient()) { HttpResponseMessage response = await client.GetAsync(_fileInfo.DownloadUrl, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using Stream contentStream = await response.Content.ReadAsStreamAsync(); using MemoryStream memoryStream = new MemoryStream(); long? length = response.Content.Headers.ContentLength; if(length == null || length == 0) { return false; } byte[] buffer = new byte[0x10000]; while(true) { int byteCount = await contentStream.ReadAsync(buffer, 0, buffer.Length); if(byteCount == 0) { break; } else { await memoryStream.WriteAsync(buffer, 0, byteCount); Dispatcher.UIThread.Post(() => { Progress = (int)((double)memoryStream.Length / length * 100); }); } } using SHA256 sha256 = SHA256.Create(); memoryStream.Position = 0; string hash = BitConverter.ToString(sha256.ComputeHash(memoryStream)).Replace("-", ""); if(hash == _fileInfo.Hash) { using ZipArchive archive = new ZipArchive(memoryStream); foreach(var entry in archive.Entries) { downloadPath += Path.GetExtension(entry.Name); entry.ExtractToFile(downloadPath, true); break; } } else { File.Delete(downloadPath); Dispatcher.UIThread.Post(() => { MesenMsgBox.Show(wnd, "AutoUpdateInvalidFile", MessageBoxButtons.OK, MessageBoxIcon.Info, _fileInfo.Hash, hash); }); return false; } } return UpdateHelper.LaunchUpdate(downloadPath); } } public class UpdateFileInfo { public string[] Platform { get; set; } = Array.Empty(); public string DownloadUrl { get; set; } = ""; public string Hash { get; set; } = ""; } public class UpdateInfo { public Version LatestVersion { get; set; } = new(); public string ReleaseNotes { get; set; } = ""; public UpdateFileInfo[] Files { get; set; } = Array.Empty(); } }