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" />
+
+
+
+
+
+
+
+
+
+
+