diff --git a/MAA.sln.DotSettings b/MAA.sln.DotSettings index 5f8667cf66..8241b4b6c4 100644 --- a/MAA.sln.DotSettings +++ b/MAA.sln.DotSettings @@ -54,6 +54,7 @@ True True True + True True True True diff --git a/src/MaaWpfGui/Constants/ConfigurationKeys.cs b/src/MaaWpfGui/Constants/ConfigurationKeys.cs index aa29cf5122..d80e117326 100644 --- a/src/MaaWpfGui/Constants/ConfigurationKeys.cs +++ b/src/MaaWpfGui/Constants/ConfigurationKeys.cs @@ -196,6 +196,8 @@ namespace MaaWpfGui.Constants public const string UpdateProxy = "VersionUpdate.Proxy"; public const string ProxyType = "VersionUpdate.ProxyType"; public const string VersionType = "VersionUpdate.VersionType"; + public const string ResourceUpdateSource = "VersionUpdate.ResourceUpdateSource"; + public const string MirrorChyanCdk = "VersionUpdate.ResourceUpdateSource.MirrorChyanCdk"; public const string UpdateCheck = "VersionUpdate.UpdateCheck"; public const string UpdateAutoCheck = "VersionUpdate.ScheduledUpdateCheck"; public const string ResourceApi = "VersionUpdate.ResourceApi"; diff --git a/src/MaaWpfGui/Constants/MaaUrls.cs b/src/MaaWpfGui/Constants/MaaUrls.cs index 4c172b30ba..9d7b18cf93 100644 --- a/src/MaaWpfGui/Constants/MaaUrls.cs +++ b/src/MaaWpfGui/Constants/MaaUrls.cs @@ -82,5 +82,11 @@ namespace MaaWpfGui.Constants "zh-tw" => $"{GitHubIssues}/new?assignees=&labels=bug&template=cn-bug-report.yaml", _ => $"{GitHubIssues}/new?assignees=&labels=bug&template=en-bug-report.yaml", }; + + // 资源更新更新源 + public const string GithubResourceUpdate = "https://github.com/MaaAssistantArknights/MaaResource/archive/refs/heads/main.zip"; + + public const string MirrorChyanWebsite = "https://mirrorc.top"; + public const string MirrorChyanResourceUpdate = $"{MirrorChyanWebsite}/api/resources/MaaResource/latest"; } } diff --git a/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs b/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs index 911a0bf655..7cd90b367d 100644 --- a/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs +++ b/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs @@ -20,19 +20,19 @@ namespace MaaWpfGui.Extensions { private static readonly ILogger _logger = Serilog.Log.ForContext("SourceContext", "HttpResponseLoggingExtension"); - public static void Log(this HttpResponseMessage response) + public static void Log(this HttpResponseMessage response, bool logUri = true) { - var method = response.RequestMessage.Method; - var url = response.RequestMessage.RequestUri.ToString(); - var statusCode = response.StatusCode.ToString(); + var method = response?.RequestMessage?.Method; + var url = response?.RequestMessage?.RequestUri?.ToString(); + var statusCode = response?.StatusCode.ToString(); - if (response.IsSuccessStatusCode) + if (response is { IsSuccessStatusCode: true }) { - _logger.Information("HTTP: {StatusCode} {Method} {Url}", statusCode, method, url); + _logger.Information("HTTP: {StatusCode} {Method} {Url}", statusCode, method, logUri ? url : "*****"); } else { - _logger.Warning("HTTP: {StatusCode} {Method} {Url}", statusCode, method, url); + _logger.Warning("HTTP: {StatusCode} {Method} {Url}", statusCode, method, logUri ? url : "*****"); } } } diff --git a/src/MaaWpfGui/Models/ResourceUpdater.cs b/src/MaaWpfGui/Models/ResourceUpdater.cs index bed86ee18f..b850f5c2d3 100644 --- a/src/MaaWpfGui/Models/ResourceUpdater.cs +++ b/src/MaaWpfGui/Models/ResourceUpdater.cs @@ -19,6 +19,8 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; +using System.Security.Cryptography; +using System.Text.Json; using System.Threading.Tasks; using System.Web; using MaaWpfGui.Constants; @@ -26,6 +28,10 @@ using MaaWpfGui.Helper; using MaaWpfGui.Main; using MaaWpfGui.ViewModels; using MaaWpfGui.ViewModels.UI; +using MaaWpfGui.ViewModels.UserControl.Settings; +using MaaWpfGui.Views.UI; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using Serilog; using Stylet; @@ -355,11 +361,14 @@ namespace MaaWpfGui.Models case UpdateResult.Success when !await HttpResponseHelper.SaveResponseToFileAsync(response, saveTo): return UpdateResult.Failed; + case UpdateResult.Success: ETagCache.Set(response); return UpdateResult.Success; + case UpdateResult.NotModified: return UpdateResult.NotModified; + case UpdateResult.Failed: default: await Task.Delay(5000); @@ -423,7 +432,7 @@ namespace MaaWpfGui.Models public static async Task UpdateFromGithubAsync() { ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceUpdating")); - bool download = await DownloadFullPackageAsync("https://github.com/MaaAssistantArknights/MaaResource/archive/refs/heads/main.zip", "MaaResource.zip").ConfigureAwait(false); + bool download = await DownloadFullPackageAsync(MaaUrls.GithubResourceUpdate, "MaaResource.zip").ConfigureAwait(false); if (!download) { ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed")); @@ -482,6 +491,118 @@ namespace MaaWpfGui.Models return true; } + // 从 MirrorChyan 下载完整包 + public static async Task UpdateFromMirrorChyanAsync() + { + // https://mirrorc.top/api/resources/MaaResource/latest?current_version=<当前版本日期,从 version.json 里拿时间戳>&cdk=&sp_id=<唯一识别码> + // 响应格式为 {"code":0,"msg":"success","data":{"version_name":"2025-01-22 14:28:32.839","version_number":9,"url":"<增量更新网址>"}} + var versionFilePath = Path.Combine(Environment.CurrentDirectory, "MirrorChyanResourceVersion"); + string? currentVersion = null; + if (File.Exists(versionFilePath)) + { + try + { + currentVersion = await File.ReadAllTextAsync(versionFilePath); + currentVersion = DateTime.Parse(currentVersion).ToString("yyyy-MM-dd+HH:mm:ss"); + } + catch (Exception e) + { + _logger.Error("Failed to read or parse version file: " + e.Message); + currentVersion = null; + } + } + else + { + _logger.Information("Version file not found, assuming first time download."); + } + + var cdk = SettingsViewModel.VersionUpdateSettings.MirrorChyanCdk; + var url = currentVersion == null + ? $"{MaaUrls.MirrorChyanResourceUpdate}?cdk={cdk}" + : $"{MaaUrls.MirrorChyanResourceUpdate}?current_version={currentVersion}&cdk={cdk}"; + + var response = await Instances.HttpService.GetAsync(new(url), logUri: false); + if (response is null) + { + ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed")); + return false; + } + + var json = await response.Content.ReadAsStringAsync(); + var data = (JObject?)JsonConvert.DeserializeObject(json); + if (data is null) + { + ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed")); + return false; + } + + var code = data["code"]?.ToString(); + if (code is not null && code != "0") + { + ToastNotification.ShowDirect(data["msg"]?.ToString()); + return false; + } + + url = data["data"]?["url"]?.ToString(); + if (string.IsNullOrEmpty(url)) + { + ToastNotification.ShowDirect(LocalizationHelper.GetString("AlreadyLatest")); + return false; + } + + ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceUpdating")); + bool download = await DownloadFullPackageAsync(url, "MaaResource.zip").ConfigureAwait(false); + if (!download) + { + ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed")); + return false; + } + + // 解压到 MaaResource 文件夹 + try + { + if (Directory.Exists("MaaResource")) + { + Directory.Delete("MaaResource", true); + } + + ZipFile.ExtractToDirectory("MaaResource.zip", "MaaResource"); + } + catch (Exception e) + { + _logger.Error("Failed to extract MaaResource.zip: " + e.Message); + ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed")); + return false; + } + + // 没有 cache 文件夹,直接复制 resource 文件夹复制到当前目录 + try + { + string sourcePath = new("MaaResource"); + string destinationPath = Directory.GetCurrentDirectory(); + DirectoryMerge(sourcePath, destinationPath); + } + catch (Exception e) + { + _logger.Error("Failed to copy folders: " + e.Message); + ToastNotification.ShowDirect(LocalizationHelper.GetString("GameResourceFailed")); + return false; + } + + // 删除 MaaResource 文件夹 和 MaaResource.zip + try + { + Directory.Delete("MaaResource", true); + File.Delete("MaaResource.zip"); + } + catch (Exception e) + { + _logger.Error("Failed to delete MaaResource: " + e.Message); + } + + return true; + } + private static async Task DownloadFullPackageAsync(string url, string saveTo) { using var response = await Instances.HttpService.GetAsync( diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index 3c981833c1..337444fcc3 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -240,6 +240,8 @@ You can cancel this popup by clicking the Settings - About Us - Issue Reporting Check update Changelog Resource update + Resource Update Source + Mirror-Chyan New Version Found Downloading in the background…… Version: diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index c6bbbb8a9b..6822e6b098 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -240,6 +240,8 @@ アップデートを確認する 変更履歴 リソース更新 + リソース更新ソース + ミラーちゃん 新しいバージョンが見つかりました バックグラウンドでダウンロードを行います…… バージョン: diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index bd4966b78a..58ce0976cc 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -240,6 +240,8 @@ 지금 업데이트 변경 내역 리소스 업데이트 + 리소스 업데이트 소스 + 미러짱 새로운 버전 확인 백그라운드에서 다운로드 중…… 버전: diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index 4d94ec40c7..ad7edc5adb 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -243,6 +243,8 @@ 检查更新 更新日志 资源更新 + 资源更新更新源 + Mirror 酱 检测到新版本 正在后台下载…… 版本号: diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index dbaa03bec0..4d3bb237fb 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -240,6 +240,8 @@ 檢查更新 更新日誌 資源更新 + 資源更新來源 + Mirror 酱 檢測到新版本 正在後臺下載…… 版本號: diff --git a/src/MaaWpfGui/Services/Web/HttpService.cs b/src/MaaWpfGui/Services/Web/HttpService.cs index c61908fbaa..c4719997ea 100755 --- a/src/MaaWpfGui/Services/Web/HttpService.cs +++ b/src/MaaWpfGui/Services/Web/HttpService.cs @@ -131,7 +131,7 @@ namespace MaaWpfGui.Services.Web return await response.Content.ReadAsStreamAsync(); } - public async Task GetAsync(Uri uri, Dictionary? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) + public async Task GetAsync(Uri uri, Dictionary? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, bool logUri = true) { try { @@ -146,7 +146,7 @@ namespace MaaWpfGui.Services.Web } var response = await _client.SendAsync(request, httpCompletionOption); - response.Log(); + response.Log(logUri); return response; } diff --git a/src/MaaWpfGui/Services/Web/IHttpService.cs b/src/MaaWpfGui/Services/Web/IHttpService.cs index 53febc8651..c770e321b2 100644 --- a/src/MaaWpfGui/Services/Web/IHttpService.cs +++ b/src/MaaWpfGui/Services/Web/IHttpService.cs @@ -53,8 +53,9 @@ namespace MaaWpfGui.Services.Web /// Target Uri /// Extra HTTP Request Headers /// The HTTP completion option + /// Whether to log uri /// object - Task GetAsync(Uri uri, Dictionary? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead); + Task GetAsync(Uri uri, Dictionary? extraHeader = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, bool logUri = true); /// /// Send HTTP POST request and a string response diff --git a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs index 2b04e6db0e..9ebad0d576 100644 --- a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs @@ -26,6 +26,7 @@ using System.Windows; using MaaWpfGui.Constants; using MaaWpfGui.Helper; using MaaWpfGui.Main; +using MaaWpfGui.Models; using MaaWpfGui.Services; using MaaWpfGui.States; using Newtonsoft.Json; @@ -465,6 +466,22 @@ public class VersionUpdateViewModel : Screen } */ + // 可以用 MirrorChyan 资源更新了喵 + switch (SettingsViewModel.VersionUpdateSettings.ResourceUpdateSource) + { + case "MirrorChyan": + if (!string.IsNullOrEmpty(SettingsViewModel.VersionUpdateSettings.MirrorChyanCdk)) + { + if (await ResourceUpdater.UpdateFromMirrorChyanAsync()) + { + SettingsViewModel.VersionUpdateSettings.IsCheckingForUpdates = false; + return CheckUpdateRetT.OnlyGameResourceUpdated; + } + } + + break; + } + SettingsViewModel.VersionUpdateSettings.IsCheckingForUpdates = false; return ret; } diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs index f2c75ac060..e1b94ddf5e 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs @@ -228,6 +228,39 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase private bool _updateCheck = Convert.ToBoolean(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.UpdateCheck, bool.TrueString)); + public List> ResourceUpdateSourceList { get; } = [ + new() { Display = "Github", Value = "Github" }, + new() { Display = LocalizationHelper.GetString("MirrorChyan"), Value = "MirrorChyan" }, + ]; + + private string _resourceUpdateSource = ConfigurationHelper.GetGlobalValue(ConfigurationKeys.ResourceUpdateSource, "Github"); + + /// + /// Gets or sets the type of version to update. + /// + public string ResourceUpdateSource + { + get => _resourceUpdateSource; + set + { + SetAndNotify(ref _resourceUpdateSource, value); + ConfigurationHelper.SetGlobalValue(ConfigurationKeys.ResourceUpdateSource, value.ToString()); + } + } + + private string _mirrorChyanCdk = SimpleEncryptionHelper.Decrypt(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.MirrorChyanCdk, string.Empty)); + + public string MirrorChyanCdk + { + get => _mirrorChyanCdk; + set + { + SetAndNotify(ref _mirrorChyanCdk, value); + value = SimpleEncryptionHelper.Encrypt(value); + ConfigurationHelper.SetGlobalValue(ConfigurationKeys.MirrorChyanCdk, value); + } + } + /// /// Gets or sets a value indicating whether to check update. /// @@ -392,7 +425,26 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase public async Task ManualUpdateResource() { IsCheckingForUpdates = true; - if (await ResourceUpdater.UpdateFromGithubAsync()) + var success = false; + switch (ResourceUpdateSource) + { + case "Github": + if (await ResourceUpdater.UpdateFromGithubAsync()) + { + success = true; + } + + break; + case "MirrorChyan": + if (await ResourceUpdater.UpdateFromMirrorChyanAsync()) + { + success = true; + } + + break; + } + + if (success) { if (AutoInstallUpdatePackage) { diff --git a/src/MaaWpfGui/Views/UserControl/Settings/VersionUpdateSettingsUserControl.xaml b/src/MaaWpfGui/Views/UserControl/Settings/VersionUpdateSettingsUserControl.xaml index 60875eee39..6233834c09 100644 --- a/src/MaaWpfGui/Views/UserControl/Settings/VersionUpdateSettingsUserControl.xaml +++ b/src/MaaWpfGui/Views/UserControl/Settings/VersionUpdateSettingsUserControl.xaml @@ -3,6 +3,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding" + xmlns:constants="clr-namespace:MaaWpfGui.Constants" xmlns:controls="clr-namespace:MaaWpfGui.Styles.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dd="urn:gong-wpf-dragdrop" @@ -72,6 +73,35 @@ ItemsSource="{Binding VersionTypeList}" SelectedValue="{Binding VersionType}" SelectedValuePath="Value" /> + + + + + + + + + + +