From 400e097fa723f74d585fd5ef79ac195e63eb7846 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 20 Feb 2023 16:04:23 +0800 Subject: [PATCH] support language switch --- src/Snap.Hutao/Snap.Hutao/App.xaml | 4 +- .../Snap.Hutao/Control/BindingProxy.cs | 2 +- .../Control/Image/CompositionImage.cs | 2 +- .../Snap.Hutao/Control/Theme/ThemeHelper.cs | 2 +- .../Snap.Hutao/Core/Caching/ImageCache.cs | 2 +- .../Core/Database/DbSetExtension.cs | 2 +- .../Core/Database/EnumerableExtension.cs | 2 +- .../Snap.Hutao/Core/Database/ISelectable.cs | 2 +- .../Core/Database/QueryableExtension.cs | 1 - .../Annotation/InjectAs.cs | 3 +- .../Annotation/InjectionAttribute.cs | 3 +- .../DependencyInjection/IocConfiguration.cs | 7 ++ .../Core/ExpressionService/CastTo.cs | 2 +- .../Core/IO/Bits/ProgressUpdateStatus.cs | 2 +- .../Core/IO/DataTransfer/Clipboard.cs | 12 ++- .../Snap.Hutao/Core/IO/StreamCopyState.cs | 31 +++++++ .../Snap.Hutao/Core/IO/StreamCopyWorker.cs | 57 +++++++++++++ .../Core/Json/Annotation/JsonSerializeType.cs | 3 +- .../Snap.Hutao/Core/LifeCycle/Activation.cs | 20 ++--- .../Snap.Hutao/Core/Setting/Localization.cs | 23 ++++++ src/Snap.Hutao/Snap.Hutao/MainWindow.xaml | 18 +++-- .../Binding/AvatarProperty/AvatarProperty.cs | 35 +++++++- .../AvatarProperty/ReliquarySubProperty.cs | 4 +- .../Model/Binding/Gacha/TypedWishSummary.cs | 16 ---- .../Snap.Hutao/Model/Entity/SettingEntry.cs | 5 ++ .../Metadata/Converter/PropertyDescriptor.cs | 10 ++- .../Resource/Localization/SH.Designer.cs | 54 +++++++++++++ .../Snap.Hutao/Resource/Localization/SH.resx | 18 +++++ .../Factory/SummaryFightPropertyMapHelper.cs | 63 ++++++++------- .../Factory/SummaryReliquaryFactory.cs | 4 +- .../Service/Game/Package/ItemOperationType.cs | 14 ++-- .../Service/Game/Package/PackageConverter.cs | 68 +++++++--------- .../Service/Game/RegistryInterop.cs | 6 +- .../Service/User/UserOptionResult.cs | 3 +- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 8 +- .../View/Control/SkillPivot.xaml.cs | 10 +-- .../View/Control/StatisticsCard.xaml | 14 +++- .../Dialog/CommunityGameRecordDialog.xaml.cs | 1 - .../View/Page/AvatarPropertyPage.xaml | 81 ++++++++++++------- .../Snap.Hutao/View/Page/SettingPage.xaml | 60 ++++++++++---- .../View/Page/SpiralAbyssRecordPage.xaml | 2 +- .../Snap.Hutao/View/Page/TestPage.xaml | 10 +++ src/Snap.Hutao/Snap.Hutao/View/UserView.xaml | 17 ++++ .../Snap.Hutao/View/WelcomeView.xaml | 26 +++--- .../ExperimentalFeaturesViewModel.cs | 5 +- .../Snap.Hutao/ViewModel/SettingViewModel.cs | 39 ++++++++- .../Snap.Hutao/ViewModel/TestViewModel.cs | 12 +++ .../Snap.Hutao/ViewModel/WelcomeViewModel.cs | 11 ++- .../Web/Bridge/MiHoYoJSInterface.cs | 1 + .../Snap.Hutao/Web/Enka/Model/Flat.cs | 2 +- .../User/{UserClient2.cs => UserClient.cs} | 0 .../Snap.Hutao/Win32/StructMarshal.cs | 4 +- 52 files changed, 581 insertions(+), 222 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Setting/Localization.cs rename src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/{UserClient2.cs => UserClient.cs} (100%) diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml index 18b7670c..efe6ecc9 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml @@ -49,9 +49,11 @@ - https://hut.ao/features/mhy-account-switch.html#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-cookie + https://hut.ao/features/mhy-account-switch.html https://hut.ao/statements/bug-report.html https://github.com/HolographicHat/GetToken/releases/latest + https://translate.hut.ao + https://afdian.net/a/DismissedLight https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png diff --git a/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs b/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs index 348ef545..2065e1eb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs @@ -12,7 +12,7 @@ namespace Snap.Hutao.Control; /// when object is not used anymore. /// [HighQuality] -public class BindingProxy : DependencyObject +internal sealed class BindingProxy : DependencyObject { private static readonly DependencyProperty DataContextProperty = Property.Depend(nameof(DataContext)); diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs index 3d3ad3a5..f46bdbe6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs @@ -20,7 +20,7 @@ namespace Snap.Hutao.Control.Image; /// 为其他图像类控件提供基类 /// [HighQuality] -public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control +internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control { private static readonly DependencyProperty SourceProperty = Property.Depend(nameof(Source), default(Uri), OnSourceChanged); private static readonly DependencyProperty EnableLazyLoadingProperty = Property.DependBoxed(nameof(EnableLazyLoading), BoxedValues.True); diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs b/src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs index 3d17f18b..b746bc2a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Control.Theme; /// 主题帮助工具类 /// [HighQuality] -public static class ThemeHelper +internal static class ThemeHelper { /// /// 判断主题是否相等 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index c64f3ebf..6018042a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -20,7 +20,7 @@ namespace Snap.Hutao.Core.Caching; [Injection(InjectAs.Singleton, typeof(IImageCache))] [HttpClient(HttpClientConfigration.Default)] [PrimaryHttpMessageHandler(MaxConnectionsPerServer = 8)] -public sealed class ImageCache : IImageCache, IImageCacheFilePathOperation +internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation { private const string CacheFolderName = nameof(ImageCache); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs index f4b080ec..aae0bc36 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs @@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Database; /// 数据库集合扩展 /// [HighQuality] -public static class DbSetExtension +internal static class DbSetExtension { /// /// 获取对应的数据库上下文 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs index c7e27b18..64e7415c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.Core.Database; /// 可枚举扩展 /// [HighQuality] -public static class EnumerableExtension +internal static class EnumerableExtension { /// /// 获取选中的值或默认值 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ISelectable.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ISelectable.cs index e0701239..ec11ca5c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ISelectable.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ISelectable.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.Database; /// 必须实现该接口 /// [HighQuality] -public interface ISelectable +internal interface ISelectable { /// /// 获取或设置当前项的选中状态 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs index 6ab9a6ae..49174089 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs @@ -19,7 +19,6 @@ internal static class QueryableExtension /// 源类型 /// 源 /// 条件 - /// 取消令牌 /// SQL返回个数 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ExecuteDeleteWhere(this IQueryable source, Expression> predicate) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs index c970b34c..dbe76305 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectAs.cs @@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation; /// /// 注入方法 /// -public enum InjectAs +[HighQuality] +internal enum InjectAs { /// /// 指示应注册为单例对象 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs index 7b703012..a92e08c5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/Annotation/InjectionAttribute.cs @@ -7,8 +7,9 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation; /// 指示被标注的类型可注入 /// 由源生成器生成注入代码 /// +[HighQuality] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public class InjectionAttribute : Attribute +internal sealed class InjectionAttribute : Attribute { /// /// 指示该类将注入为不带有接口实现的类 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs index 98ac6470..fae8578d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs @@ -3,8 +3,12 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; using System.Diagnostics; +using System.Globalization; namespace Snap.Hutao.Core.DependencyInjection; @@ -44,6 +48,9 @@ internal static class IocConfiguration #endif context.Database.Migrate(); } + + SettingEntry entry = context.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name); + Localization.Initialize(entry.Value!); } return services.AddDbContext(builder => diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExpressionService/CastTo.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExpressionService/CastTo.cs index 11a95fc9..4329b3ea 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExpressionService/CastTo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExpressionService/CastTo.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.ExpressionService; /// /// Target type [HighQuality] -public static class CastTo +internal static class CastTo { /// /// Casts to . diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs index b3893cde..0f152b78 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.IO.Bits; /// [HighQuality] [DebuggerDisplay("{BytesRead}/{TotalBytes}")] -public class ProgressUpdateStatus +internal sealed class ProgressUpdateStatus { /// /// 构造一个新的进度更新状态 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs index 0c6b7f1c..72b3fd3a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs @@ -23,10 +23,16 @@ internal static class Clipboard { await ThreadHelper.SwitchToMainThreadAsync(); DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent(); - string json = await view.GetTextAsync(); - await ThreadHelper.SwitchToBackgroundAsync(); - return JsonSerializer.Deserialize(json, options); + if (view.Contains(StandardDataFormats.Text)) + { + string json = await view.GetTextAsync(); + + await ThreadHelper.SwitchToBackgroundAsync(); + return JsonSerializer.Deserialize(json, options); + } + + return null; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs new file mode 100644 index 00000000..f830d955 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs @@ -0,0 +1,31 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.IO; + +/// +/// 流复制状态 +/// +internal sealed class StreamCopyState +{ + /// + /// 构造一个新的流复制状态 + /// + /// 已复制字节 + /// 总字节数 + public StreamCopyState(long bytesCopied, long totalBytes) + { + BytesCopied = bytesCopied; + TotalBytes = totalBytes; + } + + /// + /// 已复制字节 + /// + public long BytesCopied { get; } + + /// + /// 总字节数 + /// + public long TotalBytes { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs new file mode 100644 index 00000000..2aad5dc7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs @@ -0,0 +1,57 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.IO; + +namespace Snap.Hutao.Core.IO; + +/// +/// 流复制器 +/// +internal sealed class StreamCopyWorker +{ + private readonly Stream source; + private readonly Stream destination; + private readonly long totalBytes; + private readonly int bufferSize; + + /// + /// 创建一个新的流复制器 + /// + /// 源 + /// 目标 + /// 总字节 + /// 字节尺寸 + public StreamCopyWorker(Stream source, Stream destination, long totalBytes, int bufferSize = 81920) + { + Verify.Operation(source.CanRead, "Source Stream can't read"); + Verify.Operation(destination.CanWrite, "Destination Stream can't write"); + + this.source = source; + this.destination = destination; + this.totalBytes = totalBytes; + this.bufferSize = bufferSize; + } + + /// + /// 异步复制 + /// + /// 进度 + /// 任务 + public async Task CopyAsync(IProgress progress) + { + long totalBytesRead = 0; + int bytesRead; + Memory buffer = new byte[bufferSize]; + + do + { + bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false); + await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false); + + totalBytesRead += bytesRead; + progress.Report(new(totalBytesRead, totalBytes)); + } + while (bytesRead > 0); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Annotation/JsonSerializeType.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Annotation/JsonSerializeType.cs index e35e357f..749bf561 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Annotation/JsonSerializeType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Annotation/JsonSerializeType.cs @@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.Json.Annotation; /// /// Json 序列化类型 /// -public enum JsonSerializeType +[HighQuality] +internal enum JsonSerializeType { /// /// Int32 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index 1ca8eed0..e7b82275 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -10,6 +10,7 @@ using Snap.Hutao.Service.DailyNote; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Navigation; using System.Diagnostics; +using System.IO; using System.Security.Principal; namespace Snap.Hutao.Core.LifeCycle; @@ -25,11 +26,6 @@ internal static class Activation /// public const string Action = nameof(Action); - /// - /// 无操作 - /// - public const string NoAction = ""; - /// /// Uid /// @@ -146,7 +142,13 @@ internal static class Activation { switch (arguments) { - case NoAction: + case LaunchGame: + { + await HandleLaunchGameActionAsync().ConfigureAwait(false); + break; + } + + default: { // Increase launch times LocalSetting.Set(SettingKeys.LaunchTimes, LocalSetting.Get(SettingKeys.LaunchTimes, 0) + 1); @@ -154,12 +156,6 @@ internal static class Activation await WaitMainWindowAsync().ConfigureAwait(false); break; } - - case LaunchGame: - { - await HandleLaunchGameActionAsync().ConfigureAwait(false); - break; - } } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/Localization.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/Localization.cs new file mode 100644 index 00000000..86d4bab4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/Localization.cs @@ -0,0 +1,23 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; + +namespace Snap.Hutao.Core.Setting; + +/// +/// 本地化 +/// +internal static class Localization +{ + /// + /// 初始化本地化语言 + /// + /// 语言代码 + public static void Initialize(string culture) + { + CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture(culture); + CultureInfo.CurrentCulture = cultureInfo; + CultureInfo.CurrentUICulture = cultureInfo; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml index 83a02393..a679172d 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml @@ -15,18 +15,24 @@ Margin="48,0,0,0"/> - - - False - - - + + + + + + True + + + False + + + diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/AvatarProperty.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/AvatarProperty.cs index 917ddfbf..6d1507f8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/AvatarProperty.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/AvatarProperty.cs @@ -1,24 +1,50 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Intrinsic; +using System.Collections.Immutable; + namespace Snap.Hutao.Model.Binding.AvatarProperty; /// /// 角色属性值 /// [HighQuality] -internal sealed class AvatarProperty +internal sealed class AvatarProperty : INameIcon { + private static readonly ImmutableDictionary PropertyIcons = new Dictionary() + { + [FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CDReduce.png").ToUri(), + [FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_ChargeEfficiency.png").ToUri(), + [FightProperty.FIGHT_PROP_CRITICAL] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Critical.png").ToUri(), + [FightProperty.FIGHT_PROP_CUR_ATTACK] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CurAttack.png").ToUri(), + [FightProperty.FIGHT_PROP_CUR_DEFENSE] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CurDefense.png").ToUri(), + [FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element.png").ToUri(), + [FightProperty.FIGHT_PROP_ELEC_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Electric.png").ToUri(), + [FightProperty.FIGHT_PROP_FIRE_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Fire.png").ToUri(), + [FightProperty.FIGHT_PROP_GRASS_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Grass.png").ToUri(), + [FightProperty.FIGHT_PROP_ICE_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Ice.png").ToUri(), + [FightProperty.FIGHT_PROP_ROCK_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Rock.png").ToUri(), + [FightProperty.FIGHT_PROP_WATER_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Water.png").ToUri(), + [FightProperty.FIGHT_PROP_WIND_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Wind.png").ToUri(), + [FightProperty.FIGHT_PROP_HEAL_ADD] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Heal.png").ToUri(), + [FightProperty.FIGHT_PROP_MAX_HP] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_MaxHp.png").ToUri(), + [FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_PhysicalAttackUp.png").ToUri(), + [FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_ShieldCostMinus.png").ToUri(), + }.ToImmutableDictionary(); + /// /// 构造一个新的角色属性值 /// + /// 战斗属性 /// 名称 /// 白字 /// 绿字 - public AvatarProperty(string name, string value, string? addValue = null) + public AvatarProperty(FightProperty property, string name, string value, string? addValue = null) { Name = name; Value = value; + Icon = PropertyIcons.GetValueOrDefault(property)!; AddValue = addValue; } @@ -27,6 +53,11 @@ internal sealed class AvatarProperty /// public string Name { get; } + /// + /// 图标 + /// + public Uri Icon { get; } + /// /// 白字 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/ReliquarySubProperty.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/ReliquarySubProperty.cs index 2d12e10e..5d062d21 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/ReliquarySubProperty.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/AvatarProperty/ReliquarySubProperty.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Intrinsic; + namespace Snap.Hutao.Model.Binding.AvatarProperty; /// @@ -22,7 +24,7 @@ internal sealed class ReliquarySubProperty Score = score; // only 0.25 | 0.50 | 0.75 | 1.00 - Opacity = Math.Ceiling(score / 25) / 4; + Opacity = score == 0 ? 0.25 : Math.Ceiling(score / 25) / 4; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/TypedWishSummary.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/TypedWishSummary.cs index a7685637..087e3dd9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/TypedWishSummary.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/TypedWishSummary.cs @@ -25,22 +25,6 @@ internal sealed class TypedWishSummary : Wish get => string.Format(SH.ModelBindingGachaTypedWishSummaryMinOrangePullFormat, MinOrangePull); } - /// - /// 据上个五星抽数格式化 - /// - public string LastOrangePullFormatted - { - get => string.Format(SH.ModelBindingGachaTypedWishSummaryLastPullFormat, LastOrangePull); - } - - /// - /// 据上个四星抽数格式化 - /// - public string LastPurplePullFormatted - { - get => string.Format(SH.ModelBindingGachaTypedWishSummaryLastPullFormat, LastPurplePull); - } - /// /// 据上个五星抽数 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs index 62a244d4..eff876c2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs @@ -80,6 +80,11 @@ internal sealed class SettingEntry /// 启动游戏 目标帧率 /// public const string LaunchTargetFps = "Launch.TargetFps"; + + /// + /// 语言 + /// + public const string Culture = "Culture"; #endregion /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/PropertyDescriptor.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/PropertyDescriptor.cs index 84edfabc..b125e922 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/PropertyDescriptor.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/PropertyDescriptor.cs @@ -28,26 +28,28 @@ internal sealed class PropertyDescriptor : ValueConverter /// 格式化有绿字的角色属性 /// + /// 战斗属性 /// 属性名称 /// 方法 /// 值1 /// 值2 /// 对2 - public static AvatarProperty FormatAvatarProperty(string name, FormatMethod method, double baseValue, double addValue) + public static AvatarProperty FormatAvatarProperty(FightProperty property, string name, FormatMethod method, double baseValue, double addValue) { - return new(name, FormatValue(method, baseValue + addValue), $"[{FormatValue(method, baseValue)}+{FormatValue(method, addValue)}]"); + return new(property, name, FormatValue(method, baseValue + addValue), $"[{FormatValue(method, baseValue)}+{FormatValue(method, addValue)}]"); } /// /// 格式化无绿字的角色属性 /// + /// 战斗属性 /// 属性名称 /// 方法 /// 值 /// 对2 - public static AvatarProperty FormatAvatarProperty(string name, FormatMethod method, double value) + public static AvatarProperty FormatAvatarProperty(FightProperty property, string name, FormatMethod method, double value) { - return new(name, FormatValue(method, value)); + return new(property, name, FormatValue(method, value)); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs index 0606f855..f8f6d4e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs @@ -1248,6 +1248,15 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 重命名数据文件夹名称失败 的本地化字符串。 + /// + internal static string ServiceGamePackageRenameDataFolderFailed { + get { + return ResourceManager.GetString("ServiceGamePackageRenameDataFolderFailed", resourceCulture); + } + } + /// /// 查找类似 获取 Package Version 的本地化字符串。 /// @@ -3768,6 +3777,24 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 设置呈现语言 的本地化字符串。 + /// + internal static string ViewPageSettingApperanceLanguageDescription { + get { + return ResourceManager.GetString("ViewPageSettingApperanceLanguageDescription", resourceCulture); + } + } + + /// + /// 查找类似 语言 的本地化字符串。 + /// + internal static string ViewPageSettingApperanceLanguageHeader { + get { + return ResourceManager.GetString("ViewPageSettingApperanceLanguageHeader", resourceCulture); + } + } + /// /// 查找类似 更改窗体的背景材质 的本地化字符串。 /// @@ -4002,6 +4029,15 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 相关链接 的本地化字符串。 + /// + internal static string ViewPageSettingLinks { + get { + return ResourceManager.GetString("ViewPageSettingLinks", resourceCulture); + } + } + /// /// 查找类似 重置 的本地化字符串。 /// @@ -4083,6 +4119,15 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 赞助我们 的本地化字符串。 + /// + internal static string ViewPageSettingSponsorNavigate { + get { + return ResourceManager.GetString("ViewPageSettingSponsorNavigate", resourceCulture); + } + } + /// /// 查找类似 存储空间 的本地化字符串。 /// @@ -4110,6 +4155,15 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 贡献翻译 的本地化字符串。 + /// + internal static string ViewPageSettingTranslateNavigate { + get { + return ResourceManager.GetString("ViewPageSettingTranslateNavigate", resourceCulture); + } + } + /// /// 查找类似 前往商店 的本地化字符串。 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 0e1a4b74..16c686f4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -513,6 +513,9 @@ 在 Unity 日志文件中找不到游戏路径 + + 重命名数据文件夹名称失败 + 获取 Package Version @@ -1353,6 +1356,12 @@ 外观 + + 设置呈现语言 + + + 语言 + 更改窗体的背景材质 @@ -1431,6 +1440,9 @@ 游戏 + + 相关链接 + 重置 @@ -1458,6 +1470,9 @@ 设置游戏路径时,请选择游戏本体(YuanShen.exe 或 GenshinImpact.exe)而不是启动器(launcher.exe) + + 赞助我们 + 存储空间 @@ -1467,6 +1482,9 @@ 更改 + + 贡献翻译 + 前往商店 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFightPropertyMapHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFightPropertyMapHelper.cs index d61d9dd6..2b0f5cbd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFightPropertyMapHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFightPropertyMapHelper.cs @@ -30,16 +30,20 @@ internal static class SummaryFightPropertyMapHelper AvatarProperty defProp = GetDefProperty(fightPropMap); double em = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ELEMENT_MASTERY); // 28 - AvatarProperty emProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyEM, FormatMethod.Integer, em); + AvatarProperty emProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_ELEMENT_MASTERY, SH.ServiceAvatarInfoPropertyEM, FormatMethod.Integer, em); double critRate = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CRITICAL); // 20 - AvatarProperty critRateProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyCR, FormatMethod.Percent, critRate); + AvatarProperty critRateProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_CRITICAL, SH.ServiceAvatarInfoPropertyCR, FormatMethod.Percent, critRate); double critDMG = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CRITICAL_HURT); // 22 - AvatarProperty critDMGProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyCDmg, FormatMethod.Percent, critDMG); + AvatarProperty critDMGProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_CRITICAL_HURT, SH.ServiceAvatarInfoPropertyCDmg, FormatMethod.Percent, critDMG); double chargeEff = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); // 23 - AvatarProperty chargeEffProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyCE, FormatMethod.Percent, chargeEff); + AvatarProperty chargeEffProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, SH.ServiceAvatarInfoPropertyCE, FormatMethod.Percent, chargeEff); List properties = new(9) { hpProp, atkProp, defProp, emProp, critRateProp, critDMGProp, chargeEffProp }; @@ -50,7 +54,8 @@ internal static class SummaryFightPropertyMapHelper double value = fightPropMap[bonusProperty]; if (value > 0) { - AvatarProperty bonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(bonusProperty.GetLocalizedDescription(), FormatMethod.Percent, value); + AvatarProperty bonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + bonusProperty, bonusProperty.GetLocalizedDescription(), FormatMethod.Percent, value); properties.Add(bonusProp); } } @@ -61,7 +66,8 @@ internal static class SummaryFightPropertyMapHelper if (addValue > 0) { string description = FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.GetLocalizedDescription(); - AvatarProperty physicalBonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(description, FormatMethod.Percent, addValue); + AvatarProperty physicalBonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, description, FormatMethod.Percent, addValue); properties.Add(physicalBonusProp); } } @@ -69,36 +75,39 @@ internal static class SummaryFightPropertyMapHelper return properties; } - private static AvatarProperty GetDefProperty(Dictionary fightPropMap) - { - double baseDef = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_DEFENSE); // 7 - double def = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE); // 8 - double defPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9 - double defAdd = def + (baseDef * defPercent); - AvatarProperty defProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyDef, FormatMethod.Integer, baseDef, defAdd); - return defProp; - } - - private static AvatarProperty GetAtkProperty(Dictionary fightPropMap) - { - double baseAtk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_ATTACK); // 4 - double atk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK); // 5 - double atkPrecent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK_PERCENT); // 6 - double atkAdd = atk + (baseAtk * atkPrecent); - AvatarProperty atkProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyAtk, FormatMethod.Integer, baseAtk, atkAdd); - return atkProp; - } - private static AvatarProperty GetHpProperty(Dictionary fightPropMap) { double baseHp = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_HP); // 1 double hp = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_HP); // 2 double hpPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_HP_PERCENT); // 3 double hpAdd = hp + (baseHp * hpPercent); - AvatarProperty hpProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyHp, FormatMethod.Integer, baseHp, hpAdd); + AvatarProperty hpProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_MAX_HP, SH.ServiceAvatarInfoPropertyHp, FormatMethod.Integer, baseHp, hpAdd); return hpProp; } + private static AvatarProperty GetAtkProperty(Dictionary fightPropMap) + { + double baseAtk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_ATTACK); // 4 + double atk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK); // 5 + double atkPrecent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK_PERCENT); // 6 + double atkAdd = atk + (baseAtk * atkPrecent); + AvatarProperty atkProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_CUR_ATTACK, SH.ServiceAvatarInfoPropertyAtk, FormatMethod.Integer, baseAtk, atkAdd); + return atkProp; + } + + private static AvatarProperty GetDefProperty(Dictionary fightPropMap) + { + double baseDef = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_DEFENSE); // 7 + double def = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE); // 8 + double defPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9 + double defAdd = def + (baseDef * defPercent); + AvatarProperty defProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty( + FightProperty.FIGHT_PROP_CUR_DEFENSE, SH.ServiceAvatarInfoPropertyDef, FormatMethod.Integer, baseDef, defAdd); + return defProp; + } + private static FightProperty GetBonusFightProperty(IDictionary fightPropMap) { if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY)) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs index dfb856a7..2fa8fe84 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs @@ -45,7 +45,7 @@ internal sealed class SummaryReliquaryFactory public PropertyReliquary CreateReliquary() { MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId)); - List subProperty = equip.Reliquary!.AppendPropIdList.EmptyIfNull().Select(CreateSubProperty).ToList(); + List subProperty = equip.Reliquary!.AppendPropIdList.EmptyIfNull().SelectList(CreateSubProperty); int affixCount = GetSecondaryAffixCount(reliquary); if (subProperty.Count == 0) @@ -67,7 +67,7 @@ internal sealed class SummaryReliquaryFactory List primary = new(span[..^affixCount].ToArray()); List secondary = new(span[^affixCount..].ToArray()); - List composed = equip.Flat.ReliquarySubstats!.Select(CreateComposedSubProperty).ToList(); + List composed = equip.Flat.ReliquarySubstats!.SelectList(CreateComposedSubProperty); ReliquaryLevel relicLevel = metadataContext.ReliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Quality == reliquary.RankLevel); FightProperty property = metadataContext.IdRelicMainPropMap[equip.Reliquary.MainPropId]; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationType.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationType.cs index ada75ab9..6b09fa69 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationType.cs @@ -9,18 +9,18 @@ namespace Snap.Hutao.Service.Game.Package; [HighQuality] internal enum ItemOperationType { - /// - /// 添加 - /// - Add, - /// /// 删除 /// - Remove, + Remove = 0, /// /// 替换 /// - Replace, + Replace = 1, + + /// + /// 添加 + /// + Add = 2, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs index 789b1151..c108989d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs @@ -48,53 +48,42 @@ internal sealed class PackageConverter { await ThreadHelper.SwitchToBackgroundAsync(); string scatteredFilesUrl = gameResouce.Game.Latest.DecompressedPath; - Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version"); + Uri pkgVersionUri = $"{scatteredFilesUrl}/pkg_version".ToUri(); ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese; progress.Report(new(SH.ServiceGamePackageRequestPackageVerion)); - Dictionary remoteItems = default!; + Dictionary remoteItems; try { using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false)) { - remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false); + remoteItems = await GetRemoteVersionItemsAsync(remoteSteam).ConfigureAwait(false); } } catch (IOException ex) { - ThrowHelper.PackageConvert(SH.ServiceGamePackageRequestPackageVerionFailed, ex); + throw ThrowHelper.PackageConvert(SH.ServiceGamePackageRequestPackageVerionFailed, ex); } Dictionary localItems; using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version"))) { - localItems = await GetVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false); + localItems = await GetLocalVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false); } - IEnumerable diffOperations = GetItemOperationInfos(remoteItems, localItems); + IEnumerable diffOperations = GetItemOperationInfos(remoteItems, localItems).OrderBy(i => (int)i.Type); return await ReplaceGameResourceAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false); } /// /// 检查过时文件与Sdk + /// 只在国服环境有效 /// /// 游戏资源 /// 游戏文件夹 /// 任务 public async Task EnsureDeprecatedFilesAndSdkAsync(GameResource resource, string gameFolder) { - if (resource.DeprecatedFiles != null) - { - foreach (NameMd5 file in resource.DeprecatedFiles) - { - string filePath = Path.Combine(gameFolder, file.Name); - if (File.Exists(filePath)) - { - File.Move(filePath, $"{filePath}.backup"); - } - } - } - string sdkDllBackup = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll.backup"); string sdkDll = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll"); string sdkVersionBackup = Path.Combine(gameFolder, YuanShenData, "sdk_pkg_version.backup"); @@ -103,6 +92,7 @@ internal sealed class PackageConverter // Only bilibili's sdk is not null if (resource.Sdk != null) { + // TODO: verify sdk md5 if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup)) { FileOperation.Move(sdkDllBackup, sdkDll, false); @@ -133,6 +123,15 @@ internal sealed class PackageConverter FileOperation.Move(sdkDll, sdkDllBackup, true); FileOperation.Move(sdkVersion, sdkVersionBackup, true); } + + if (resource.DeprecatedFiles != null) + { + foreach (NameMd5 file in resource.DeprecatedFiles) + { + string filePath = Path.Combine(gameFolder, file.Name); + FileOperation.Move(filePath, $"{filePath}.backup", true); + } + } } private static string ConvertRemoteName(string remoteName, ConvertDirection direction) @@ -216,6 +215,7 @@ internal sealed class PackageConverter long totalBytesRead = 0; int bytesRead; Memory buffer = new byte[bufferSize]; + do { bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false); @@ -223,11 +223,6 @@ internal sealed class PackageConverter totalBytesRead += bytesRead; progress.Report(new(name, totalBytesRead, totalBytes)); - - if (bytesRead <= 0) - { - break; - } } while (bytesRead > 0); } @@ -239,11 +234,11 @@ internal sealed class PackageConverter { RenameDataFolder(gameFolder, direction); } - catch (IOException) + catch (IOException ex) { // Access to the path is denied. // When user install the game in special folder like 'Program Files' - return false; + throw ThrowHelper.GameFileOperation(SH.ServiceGamePackageRenameDataFolderFailed, ex); } // Cache folder @@ -260,20 +255,16 @@ internal sealed class PackageConverter switch (info.Type) { - case ItemOperationType.Add: - await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false); - break; - case ItemOperationType.Replace: - { - MoveToCache(moveToFilePath, targetFilePath); - await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false); - break; - } - case ItemOperationType.Remove: MoveToCache(moveToFilePath, targetFilePath); break; - + case ItemOperationType.Replace: + MoveToCache(moveToFilePath, targetFilePath); + await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false); + break; + case ItemOperationType.Add: + await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false); + break; default: break; } @@ -316,7 +307,6 @@ internal sealed class PackageConverter try { await CopyToWithProgressAsync(webStream, fileStream, info.Target, totalBytes, progress).ConfigureAwait(false); - fileStream.Seek(0, SeekOrigin.Begin); string remoteMd5 = await Digest.GetStreamMD5Async(fileStream).ConfigureAwait(false); if (info.Md5 == remoteMd5.ToLowerInvariant()) { @@ -359,7 +349,7 @@ internal sealed class PackageConverter } } - private async Task> GetVersionItemsAsync(Stream stream) + private async Task> GetRemoteVersionItemsAsync(Stream stream) { Dictionary results = new(); using (StreamReader reader = new(stream)) @@ -377,7 +367,7 @@ internal sealed class PackageConverter return results; } - private async Task> GetVersionItemsAsync(Stream stream, ConvertDirection direction, Func nameConverter) + private async Task> GetLocalVersionItemsAsync(Stream stream, ConvertDirection direction, Func nameConverter) { Dictionary results = new(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/RegistryInterop.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/RegistryInterop.cs index b9e68c1b..5d11e999 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/RegistryInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/RegistryInterop.cs @@ -44,14 +44,12 @@ internal static class RegistryInterop Set-ItemProperty -Path '{path}' -Name '{SdkKey}' -Value $value -Force; """; - string psExecutablePath = @"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"; - ProcessStartInfo startInfo = new() { Arguments = command, - WorkingDirectory = Path.GetDirectoryName(psExecutablePath), + WorkingDirectory = Path.GetDirectoryName(PsExecutablePath), CreateNoWindow = true, - FileName = psExecutablePath, + FileName = PsExecutablePath, }; try diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs index 5355843d..028c0b2d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs @@ -6,7 +6,8 @@ namespace Snap.Hutao.Service.User; /// /// 用户添加操作结果 /// -public enum UserOptionResult +[HighQuality] +internal enum UserOptionResult { /// /// 添加成功 diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 2cced7f3..f7ce0041 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -160,15 +160,15 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -184,7 +184,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs index f6634c8a..a0b1f24d 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/SkillPivot.xaml.cs @@ -4,7 +4,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control; -using Snap.Hutao.Model.Metadata.Avatar; +using System.Collections; namespace Snap.Hutao.View.Control; @@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Control; [HighQuality] internal sealed partial class SkillPivot : UserControl { - private static readonly DependencyProperty SkillsProperty = Property.Depend>(nameof(Skills)); + private static readonly DependencyProperty SkillsProperty = Property.Depend(nameof(Skills)); private static readonly DependencyProperty SelectedProperty = Property.Depend(nameof(Selected)); private static readonly DependencyProperty ItemTemplateProperty = Property.Depend(nameof(ItemTemplate)); @@ -29,9 +29,9 @@ internal sealed partial class SkillPivot : UserControl /// /// 技能列表 /// - public List Skills + public IList Skills { - get => (List)GetValue(SkillsProperty); + get => (IList)GetValue(SkillsProperty); set => SetValue(SkillsProperty, value); } @@ -52,4 +52,4 @@ internal sealed partial class SkillPivot : UserControl get => (DataTemplate)GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml index a730133a..27fd1954 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml @@ -119,11 +119,21 @@ + + + + - + Text="{Binding Name}" + TextTrimming="CharacterEllipsis" + TextWrapping="NoWrap"/> + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml index 9effe0ff..8578d5b7 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml @@ -254,6 +254,7 @@ MaxWidth="800" HorizontalAlignment="Left" Background="Transparent"> + @@ -537,41 +538,63 @@ + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -581,7 +604,7 @@ HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"> - + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index d1c3fc5b..6fbca063 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -2,6 +2,7 @@ x:Class="Snap.Hutao.View.Page.SettingPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:shc="using:Snap.Hutao.Control" @@ -16,7 +17,7 @@ diff --git a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml index 5b7e314e..bb44dfe2 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml @@ -42,18 +42,20 @@ - - - - - + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs index ac381375..b3e0c2f3 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/ExperimentalFeaturesViewModel.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Service.Abstraction; using Windows.Storage; @@ -65,9 +66,7 @@ internal sealed class ExperimentalFeaturesViewModel : ObservableObject { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); await appDbContext.Users.ExecuteDeleteAsync().ConfigureAwait(false); - - IInfoBarService infoBarService = scope.ServiceProvider.GetRequiredService(); - infoBarService.Success(SH.ViewModelExperimentalDeleteUserSuccess); + AppInstance.Restart(string.Empty); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs index 4abf453f..ea1dc276 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.IO; using Snap.Hutao.Core.Setting; @@ -17,6 +18,7 @@ using Snap.Hutao.Service.GachaLog.QueryProvider; using Snap.Hutao.Service.Game; using Snap.Hutao.Service.Game.Locator; using Snap.Hutao.View.Dialog; +using System.Globalization; using System.IO; using Windows.Storage.Pickers; @@ -42,9 +44,18 @@ internal sealed class SettingViewModel : Abstraction.ViewModel new("MicaAlt", BackdropType.MicaAlt), }; + private readonly List> cultures = new() + { + new("简体中文", "zh-CN"), + new("繁體中文", "zh-TW"), + new("English (United States)", "en-US"), + new("한국인", "ko-KR"), + }; + private bool isEmptyHistoryWishVisible; private string gamePath; private NameValue selectedBackdropType; + private NameValue? selectedCulture; /// /// 构造一个新的设置视图模型 @@ -61,6 +72,9 @@ internal sealed class SettingViewModel : Abstraction.ViewModel isEmptyHistoryWishVisibleEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.IsEmptyHistoryWishVisible, Core.StringLiterals.False); IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!); + string? cultureName = appDbContext.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name).Value; + selectedCulture = cultures.FirstOrDefault(c => c.Value == cultureName); + selectedBackdropTypeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString()); BackdropType type = Enum.Parse(selectedBackdropTypeEntry.Value!); @@ -154,6 +168,29 @@ internal sealed class SettingViewModel : Abstraction.ViewModel } } + /// + /// 语言 + /// + public List> Cultures { get => cultures; } + + /// + /// 选中的语言 + /// + public NameValue? SelectedCulture + { + get => selectedCulture; + set + { + if (SetProperty(ref selectedCulture, value)) + { + SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name); + entry.Value = selectedCulture.Value; + appDbContext.Settings.UpdateAndSave(entry); + AppInstance.Restart(string.Empty); + } + } + } + /// /// 实验性功能 /// @@ -271,6 +308,6 @@ internal sealed class SettingViewModel : Abstraction.ViewModel private void ResetStaticResource() { StaticResource.UnfulfillAllContracts(); - serviceProvider.GetRequiredService().Success(SH.ViewPageSettingResetSuccessMessage); + AppInstance.Restart(string.Empty); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index bbadb76a..12f34e24 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO.Bits; using Snap.Hutao.View.Dialog; @@ -29,6 +30,7 @@ internal sealed class TestViewModel : Abstraction.ViewModel ShowCommunityGameRecordDialogCommand = new AsyncRelayCommand(ShowCommunityGameRecordDialogAsync); ShowAdoptCalculatorDialogCommand = new AsyncRelayCommand(ShowAdoptCalculatorDialogAsync); DownloadStaticFileCommand = new AsyncRelayCommand(DownloadStaticFileAsync); + RestartAppCommand = new RelayCommand(RestartApp); } /// @@ -46,6 +48,11 @@ internal sealed class TestViewModel : Abstraction.ViewModel /// public ICommand DownloadStaticFileCommand { get; } + /// + /// 重启命令 + /// + public ICommand RestartAppCommand { get; } + private async Task ShowCommunityGameRecordDialogAsync() { // ContentDialog must be created by main thread. @@ -80,4 +87,9 @@ internal sealed class TestViewModel : Abstraction.ViewModel } } } + + private void RestartApp(bool elevated) + { + AppInstance.Restart(string.Empty); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs index 1568ded4..02a80573 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs @@ -57,12 +57,11 @@ internal sealed class WelcomeViewModel : ObservableObject // Cancel all previous created jobs serviceProvider.GetRequiredService().CancelAllJobs(); - await Task.WhenAll(downloadSummaries.Select(async downloadTask => + await Parallel.ForEachAsync(downloadSummaries, async (summary, token) => { - await downloadTask.DownloadAndExtractAsync().ConfigureAwait(false); - await ThreadHelper.SwitchToMainThreadAsync(); - DownloadSummaries.Remove(downloadTask); - })).ConfigureAwait(true); + await summary.DownloadAndExtractAsync().ConfigureAwait(false); + ThreadHelper.InvokeOnMainThread(() => DownloadSummaries.Remove(summary)); + }).ConfigureAwait(true); serviceProvider.GetRequiredService().Send(new Message.WelcomeStateCompleteMessage()); StaticResource.FulfillAllContracts(); @@ -123,7 +122,7 @@ internal sealed class WelcomeViewModel : ObservableObject /// 下载信息 /// [SuppressMessage("", "CA1067")] - public class DownloadSummary : ObservableObject, IEquatable + internal sealed class DownloadSummary : ObservableObject, IEquatable { private readonly IServiceProvider serviceProvider; private readonly BitsManager bitsManager; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs index cfccb202..70c2399b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs @@ -202,6 +202,7 @@ internal class MiHoYoJSInterface await userService.RefreshCookieTokenAsync(user).ConfigureAwait(false); } + await ThreadHelper.SwitchToMainThreadAsync(); webView.SetCookie(user.CookieToken, user.Ltoken); return new() { Data = new() { [Cookie.COOKIE_TOKEN] = user.CookieToken![Cookie.COOKIE_TOKEN] } }; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/Flat.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/Flat.cs index fe855bc6..4049eb1e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/Flat.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/Flat.cs @@ -44,7 +44,7 @@ internal sealed class Flat /// List of Artifact Substats /// [JsonPropertyName("reliquarySubstats")] - public IList? ReliquarySubstats { get; set; } + public List? ReliquarySubstats { get; set; } /// /// 物品类型 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs similarity index 100% rename from src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient2.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs b/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs index 821d0e59..fa1c2c1f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs @@ -42,9 +42,7 @@ internal static class StructMarshal public static unsafe Windows.UI.Color Color(uint value) { Unsafe.SkipInit(out Windows.UI.Color color); - uint reversed = BinaryPrimitives.ReverseEndianness(value); - Unsafe.WriteUnaligned(&color, reversed); - + *(uint*)&color = BinaryPrimitives.ReverseEndianness(value); // Unsafe.WriteUnaligned(&color, reversed); return color; }