mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d631b54b4 | ||
|
|
b0d5051957 | ||
|
|
6a42c36a76 | ||
|
|
7cf106ec50 | ||
|
|
f12cd63c92 | ||
|
|
c441fdb6b0 | ||
|
|
09a880525b | ||
|
|
15212d8f21 | ||
|
|
a60c4bff08 | ||
|
|
e02985926d | ||
|
|
09448b7137 | ||
|
|
6487df776a | ||
|
|
2be11c22df | ||
|
|
9ecb3d5821 | ||
|
|
ca64c3e0ef | ||
|
|
3fe726aa63 | ||
|
|
6b7ffe9fe9 | ||
|
|
5ed5729c4e | ||
|
|
9ba0066f40 | ||
|
|
9c639fbaa4 | ||
|
|
a592816661 | ||
|
|
ec9c5ebee1 | ||
|
|
9475c19b64 | ||
|
|
9bfe7f78ef | ||
|
|
88d7f0bcc7 | ||
|
|
4920da4ea2 | ||
|
|
99b2ccb33b | ||
|
|
9b94a75d6f | ||
|
|
e390ad2839 | ||
|
|
3086d59674 | ||
|
|
cb00fdbda0 | ||
|
|
1702dfcdc6 | ||
|
|
4b54b343f9 | ||
|
|
292b21a759 | ||
|
|
29e9413022 | ||
|
|
7c923aaa5e | ||
|
|
c6618be0fc | ||
|
|
45dd276b89 | ||
|
|
8a9fb38f49 | ||
|
|
1249216491 | ||
|
|
4bf3f4151e | ||
|
|
89d909b04f | ||
|
|
2cec0f5e0e | ||
|
|
bbb97cd802 | ||
|
|
67b058f126 | ||
|
|
b98611ccd9 | ||
|
|
80d6d5eb2b | ||
|
|
f682bb57e8 | ||
|
|
486c6eb308 | ||
|
|
0629f7c4c9 | ||
|
|
b49288a98f | ||
|
|
ee99d0b665 | ||
|
|
72b62aa9c6 | ||
|
|
6b031e1866 | ||
|
|
59c03c7f3b | ||
|
|
c03a96b44f | ||
|
|
d5a97903d3 | ||
|
|
7f998dc87f | ||
|
|
2367c4759d | ||
|
|
043e3f07d8 | ||
|
|
cd075c4dab | ||
|
|
65252f1f69 | ||
|
|
f5dd5f4c1d | ||
|
|
27ce55f3f7 | ||
|
|
311941bb89 | ||
|
|
07e2489cab | ||
|
|
699ac60aaf | ||
|
|
dc7bc7e35d | ||
|
|
bf67fcf3a2 | ||
|
|
5093246571 | ||
|
|
94fe192581 | ||
|
|
c679032387 | ||
|
|
d119b056c7 | ||
|
|
ab95ce8ce8 | ||
|
|
0ede5b158f | ||
|
|
6c9a98c2c9 | ||
|
|
a725fc0e9e | ||
|
|
5251dd9343 | ||
|
|
0b71053bf9 | ||
|
|
a91499171d | ||
|
|
e041def070 | ||
|
|
2e81ddf3f4 | ||
|
|
4d988e5500 | ||
|
|
36aff679c0 | ||
|
|
d490393108 | ||
|
|
1633f2c668 | ||
|
|
241122eadc | ||
|
|
339bb9292b | ||
|
|
4e27d40b76 | ||
|
|
4f42a299d1 | ||
|
|
80aad5f09e | ||
|
|
35370c392b | ||
|
|
115dae8966 | ||
|
|
795e9730b9 | ||
|
|
7dac0d6f73 | ||
|
|
7c42b7e8ed | ||
|
|
3c766f55b5 | ||
|
|
38a07caeab | ||
|
|
5fb55ea645 | ||
|
|
aba568d8c6 | ||
|
|
116bd58a81 | ||
|
|
46cc5f7f1e | ||
|
|
3e5ca2440b | ||
|
|
081a60f3f8 | ||
|
|
4a5ddd872d | ||
|
|
81e5159615 | ||
|
|
d5eec58157 | ||
|
|
0242d3d0ca | ||
|
|
3fcb4caaf1 | ||
|
|
ae2ddee6c4 | ||
|
|
089938c1c0 | ||
|
|
c515f70e95 | ||
|
|
317b68b1ce | ||
|
|
7b5a6ccae0 | ||
|
|
13108b6694 | ||
|
|
c4ff359995 | ||
|
|
70399f7a7d | ||
|
|
3d1a365079 | ||
|
|
2b6d4af5a3 | ||
|
|
80450660e4 | ||
|
|
28b09d56f3 | ||
|
|
995860463f | ||
|
|
474d06457c | ||
|
|
7534729794 | ||
|
|
167d66aa67 | ||
|
|
e20a9f3ff4 | ||
|
|
868232d98f | ||
|
|
ab1b3c02c8 | ||
|
|
8ef5dc9302 | ||
|
|
4c7e02cd5c | ||
|
|
d8e0d74be6 | ||
|
|
655f97353c | ||
|
|
a387e0dbf5 | ||
|
|
8111c1e662 | ||
|
|
9cd9193425 | ||
|
|
d531c81fa2 | ||
|
|
1d0a72493e | ||
|
|
f2b361819b | ||
|
|
41f7245a1a | ||
|
|
889e914f7f | ||
|
|
9f90ec221c | ||
|
|
8fc874fd09 | ||
|
|
f42ec1ea12 | ||
|
|
5cc3cf264c | ||
|
|
e38517a2ad | ||
|
|
cdc0fb8a82 | ||
|
|
d50a6df14c | ||
|
|
4886904530 | ||
|
|
ac34376c13 | ||
|
|
3bcee3d149 | ||
|
|
3d3b03851e | ||
|
|
dc64302424 | ||
|
|
db3a611c6e |
30
README.md
30
README.md
@@ -1,42 +1,36 @@
|
||||

|
||||

|
||||
|
||||
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新功能相结合,提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
|
||||
Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed for modern Windows platform to improve the gaming experience for desktop players. By combining existing official resources with new features designed by the development team, it provides a complete and useful set of tools without the need to rely on mobile devices. Snap Hutao does not take any destructive modification to the game client to ensure the security of the toolkit.
|
||||
|
||||
## 下载使用 / Download
|
||||
## 安装 / Installation
|
||||
|
||||
 [](https://github.com/DGP-Studio/Snap.Hutao/releases/latest) []()
|
||||
|
||||
---
|
||||
|
||||
#### 使用安装器安装 / Install with Snap.Hutao.Depolyment Installer
|
||||
你可以按照[快速开始](https://hut.ao/zh/quick-start.html)文档中提供的流程安装并设置 Snap Hutao。
|
||||
|
||||
Snap.Hutao.Depolyment 是一个由 DGP-Studio 重新包装的 Windows 应用安装器,适用于缺少专业计算机知识的一般用户,可以在安装时同时解决缺少必要系统环境的问题。
|
||||
You can follow the instructions in the [Quick Start](https://hut.ao/en/quick-start.html) document to install and set up Snap Hutao.
|
||||
|
||||
Snap.Hutao.Depolyment is a Windows application installer repackaged by DGP-Studio for the users who lacks computer knowledge and can solve the problem of missing necessary system environment at the same time as the installation.
|
||||
## 本地化翻译 / Localization
|
||||
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao.Deployment/releases/latest)
|
||||
].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)
|
||||
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao.Deployment/-/releases)
|
||||
Snap Hutao 使用 [Crowdin](https://translate.hut.ao/) 作为客户端文本翻译平台,在该平台上你可以为你熟悉的语言提交翻译文本。我们感谢每一个为 Snap Hutao 做出贡献的社区成员,并且欢迎更多的朋友能参与到这个项目中。
|
||||
|
||||
#### 使用 MSIX 包安装 / Install with MSIX Package
|
||||
Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translation platform where you can submit translated text for languages you are familiar with. We are grateful to every community member who has contributed to Snap Hutao and welcome more friends to participate in this project.
|
||||
|
||||
直接使用 Snap Hutao MSIX 安装包,使用 Windows 内置的 App Installer 即可安装。如在安装中出现问题,请查阅我们的[常见问题](https://hut.ao/zh/advanced/FAQ.html)文档
|
||||
## 社区 / Community
|
||||
|
||||
Install with Snap Hutao MSIX package, can be installed with Windows built-in App Installer. If you faced any issue, please check our [FAQ](https://hut.ao/en/advanced/FAQ.html) document.
|
||||
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao/releases/latest)
|
||||
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao/-/releases)
|
||||
[](https://discord.gg/CcH5XtDtvR) [](https://qm.qq.com/q/WJKykrY9W)
|
||||
|
||||
## 贡献 / Contribute
|
||||
|
||||
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
|
||||
* [在 Crowdin 上进行本地化 / Translate Project on Crowdin](https://translate.hut.ao/)
|
||||
* [向我们提交 PR / Make Pull Requests](https://hut.ao/development/contribute.html)
|
||||
* [为我们更新文档 / Enhance our Document](https://github.com/DGP-Studio/Snap.Hutao.Docs)
|
||||
* [帮助我们测试程序 / Test Binary Package](https://hut.ao/development/contribute.html)
|
||||
|
||||
## 特别感谢 / Special Thanks
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| >=1.6.0 | :white_check_mark: |
|
||||
| <1.6.0 | :x: |
|
||||
| >=1.9.0 | :white_check_mark: |
|
||||
| <1.9.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
BIN
res/Banner3-large-cn.psd
Normal file
BIN
res/Banner3-large-cn.psd
Normal file
Binary file not shown.
BIN
res/Banner3-large.psd
Normal file
BIN
res/Banner3-large.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/abyss.psd
Normal file
BIN
res/Store/chs/abyss.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/achievement.psd
Normal file
BIN
res/Store/chs/achievement.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/character-data.psd
Normal file
BIN
res/Store/chs/character-data.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/lancher.psd
Normal file
BIN
res/Store/chs/lancher.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/realtime-notes.psd
Normal file
BIN
res/Store/chs/realtime-notes.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/wish.psd
Normal file
BIN
res/Store/chs/wish.psd
Normal file
Binary file not shown.
BIN
res/Store/en/abyss.psd
Normal file
BIN
res/Store/en/abyss.psd
Normal file
Binary file not shown.
BIN
res/Store/en/achievement.psd
Normal file
BIN
res/Store/en/achievement.psd
Normal file
Binary file not shown.
BIN
res/Store/en/character-data.psd
Normal file
BIN
res/Store/en/character-data.psd
Normal file
Binary file not shown.
BIN
res/Store/en/lancher.psd
Normal file
BIN
res/Store/en/lancher.psd
Normal file
Binary file not shown.
BIN
res/Store/en/realtime-notes.psd
Normal file
BIN
res/Store/en/realtime-notes.psd
Normal file
Binary file not shown.
BIN
res/Store/en/wish.psd
Normal file
BIN
res/Store/en/wish.psd
Normal file
Binary file not shown.
@@ -110,7 +110,6 @@ dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
dotnet_diagnostic.IDE0290.severity = none
|
||||
|
||||
# SA1208: System using directives should be placed before other using directives
|
||||
dotnet_diagnostic.SA1208.severity = none
|
||||
@@ -321,7 +320,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
|
||||
# CA2251: 使用 “string.Equals”
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
|
||||
csharp_style_prefer_primary_constructors = false:none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace Snap.Hutao.Test.PlatformExtensions;
|
||||
@@ -11,6 +12,7 @@ public sealed class DependencyInjectionTest
|
||||
.AddSingleton<IService, ServiceB>()
|
||||
.AddScoped<IScopedService, ServiceA>()
|
||||
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
|
||||
.AddLogging(builder => builder.AddConsole())
|
||||
.BuildServiceProvider();
|
||||
|
||||
[TestMethod]
|
||||
@@ -41,6 +43,13 @@ public sealed class DependencyInjectionTest
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoggerWithInterfaceTypeCanBeResolved()
|
||||
{
|
||||
Assert.IsNotNull(services.GetService<ILogger<IScopedService>>());
|
||||
Assert.IsNotNull(services.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(IScopedService)));
|
||||
}
|
||||
|
||||
private interface IService
|
||||
{
|
||||
Guid Id { get; }
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.1">
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using System.Diagnostics;
|
||||
|
||||
@@ -21,17 +22,17 @@ namespace Snap.Hutao;
|
||||
[SuppressMessage("", "SH001")]
|
||||
public sealed partial class App : Application
|
||||
{
|
||||
private const string ConsoleBanner = """
|
||||
private const string ConsoleBanner = $"""
|
||||
----------------------------------------------------------------
|
||||
_____ _ _ _
|
||||
/ ____| | | | | | |
|
||||
| (___ _ __ __ _ _ __ | |__| | _ _ | |_ __ _ ___
|
||||
\___ \ | '_ \ / _` || '_ \ | __ || | | || __|/ _` | / _ \
|
||||
_____ _ _ _
|
||||
/ ____| | | | | | |
|
||||
| (___ _ __ __ _ _ __ | |__| | _ _ | |_ __ _ ___
|
||||
\___ \ | '_ \ / _` || '_ \ | __ || | | || __|/ _` | / _ \
|
||||
____) || | | || (_| || |_) |_ | | | || |_| || |_| (_| || (_) |
|
||||
|_____/ |_| |_| \__,_|| .__/(_)|_| |_| \__,_| \__|\__,_| \___/
|
||||
| |
|
||||
|_|
|
||||
|
||||
|_____/ |_| |_| \__,_|| .__/(_)|_| |_| \__,_| \__|\__,_| \___/
|
||||
| |
|
||||
|_|
|
||||
|
||||
Snap.Hutao is a open source software developed by DGP Studio.
|
||||
Copyright (C) 2022 - 2024 DGP Studio, All Rights Reserved.
|
||||
----------------------------------------------------------------
|
||||
@@ -69,7 +70,7 @@ public sealed partial class App : Application
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation(ConsoleBanner);
|
||||
logger.LogColorizedInformation((ConsoleBanner, ConsoleColor.DarkYellow));
|
||||
LogDiagnosticInformation();
|
||||
|
||||
// manually invoke
|
||||
@@ -89,8 +90,8 @@ public sealed partial class App : Application
|
||||
{
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
logger.LogInformation("FamilyName: {name}", runtimeOptions.FamilyName);
|
||||
logger.LogInformation("Version: {version}", runtimeOptions.Version);
|
||||
logger.LogInformation("LocalCache: {folder}", runtimeOptions.LocalCache);
|
||||
logger.LogColorizedInformation(("FamilyName: {Name}", ConsoleColor.Blue), (runtimeOptions.FamilyName, ConsoleColor.Cyan));
|
||||
logger.LogColorizedInformation(("Version: {Version}", ConsoleColor.Blue), (runtimeOptions.Version, ConsoleColor.Cyan));
|
||||
logger.LogColorizedInformation(("LocalCache: {Path}", ConsoleColor.Blue), (runtimeOptions.LocalCache, ConsoleColor.Cyan));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using System.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.AutoSuggestBox;
|
||||
|
||||
@@ -21,8 +22,8 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
|
||||
TextChanged += OnFilterSuggestionRequested;
|
||||
QuerySubmitted += OnQuerySubmitted;
|
||||
TokenItemAdding += OnTokenItemAdding;
|
||||
TokenItemAdded += OnTokenItemModified;
|
||||
TokenItemRemoved += OnTokenItemModified;
|
||||
TokenItemAdded += OnTokenItemCollectionChanged;
|
||||
TokenItemRemoved += OnTokenItemCollectionChanged;
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
@@ -30,12 +31,15 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
|
||||
{
|
||||
if (this.FindDescendant("SuggestionsPopup") is Popup { Child: Border { Child: ListView listView } border })
|
||||
{
|
||||
IAppResourceProvider appResourceProvider = Ioc.Default.GetRequiredService<IAppResourceProvider>();
|
||||
IAppResourceProvider appResourceProvider = this.ServiceProvider().GetRequiredService<IAppResourceProvider>();
|
||||
|
||||
listView.Background = null;
|
||||
listView.Margin = appResourceProvider.GetResource<Thickness>("AutoSuggestListPadding");
|
||||
|
||||
border.Background = appResourceProvider.GetResource<Microsoft.UI.Xaml.Media.Brush>("AutoSuggestBoxSuggestionsListBackground");
|
||||
border.CornerRadius = new(0, 0, 8, 8);
|
||||
CornerRadius overlayCornerRadius = appResourceProvider.GetResource<CornerRadius>("OverlayCornerRadius");
|
||||
CornerRadiusFilterConverter cornerRadiusFilterConverter = new() { Filter = CornerRadiusFilterKind.Bottom };
|
||||
border.CornerRadius = (CornerRadius)cornerRadiusFilterConverter.Convert(overlayCornerRadius, typeof(CornerRadius), default, default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,12 +47,19 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
{
|
||||
return;
|
||||
sender.ItemsSource = AvailableTokens
|
||||
.OrderBy(kvp => kvp.Value.Kind)
|
||||
.Select(kvp => kvp.Value);
|
||||
}
|
||||
|
||||
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase));
|
||||
sender.ItemsSource = AvailableTokens
|
||||
.Where(kvp => kvp.Value.Value.Contains(Text, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(kvp => kvp.Value.Kind)
|
||||
.ThenBy(kvp => kvp.Value.Order)
|
||||
.Select(kvp => kvp.Value)
|
||||
.DefaultIfEmpty(SearchToken.NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +80,23 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
|
||||
return;
|
||||
}
|
||||
|
||||
args.Item = AvailableTokens.GetValueOrDefault(args.TokenText) ?? new SearchToken(SearchTokenKind.None, args.TokenText);
|
||||
if (AvailableTokens.GetValueOrDefault(args.TokenText) is { } token)
|
||||
{
|
||||
args.Item = token;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTokenItemModified(TokenizingTextBox sender, object args)
|
||||
private void OnTokenItemCollectionChanged(TokenizingTextBox sender, object args)
|
||||
{
|
||||
CommandInvocation.TryExecute(FilterCommand, FilterCommandParameter);
|
||||
if (args is SearchToken { Kind: SearchTokenKind.None } token)
|
||||
{
|
||||
((IList)sender.ItemsSource).Remove(token);
|
||||
}
|
||||
|
||||
FilterCommand.TryExecute(FilterCommandParameter);
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,16 @@ namespace Snap.Hutao.Control.AutoSuggestBox;
|
||||
|
||||
internal sealed class SearchToken
|
||||
{
|
||||
public SearchToken(SearchTokenKind kind, string value, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null)
|
||||
public static readonly SearchToken NotFound = new(SearchTokenKind.None, SH.ControlAutoSuggestBoxNotFoundValue, 0);
|
||||
|
||||
public SearchToken(SearchTokenKind kind, string value, int order, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null)
|
||||
{
|
||||
Value = value;
|
||||
Kind = kind;
|
||||
IconUri = iconUri;
|
||||
SideIconUri = sideIconUri;
|
||||
Quality = quality;
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public SearchTokenKind Kind { get; }
|
||||
@@ -26,6 +29,8 @@ internal sealed class SearchToken
|
||||
|
||||
public Color? Quality { get; }
|
||||
|
||||
public int Order { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
|
||||
@@ -6,12 +6,12 @@ namespace Snap.Hutao.Control.AutoSuggestBox;
|
||||
internal enum SearchTokenKind
|
||||
{
|
||||
None,
|
||||
AssociationType,
|
||||
Avatar,
|
||||
BodyType,
|
||||
ElementName,
|
||||
FightProperty,
|
||||
ItemQuality,
|
||||
Weapon,
|
||||
WeaponType,
|
||||
FightProperty,
|
||||
ElementName,
|
||||
AssociationType,
|
||||
BodyType,
|
||||
Avatar,
|
||||
Weapon,
|
||||
}
|
||||
@@ -33,6 +33,7 @@ internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavio
|
||||
|
||||
protected override bool Uninitialize()
|
||||
{
|
||||
periodicTimerCancellationTokenSource.Cancel();
|
||||
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,4 @@ namespace Snap.Hutao.Control;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("DataContext", typeof(object))]
|
||||
internal sealed partial class BindingProxy : DependencyObject
|
||||
{
|
||||
}
|
||||
internal sealed partial class BindingProxy : DependencyObject;
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal class ButtonBaseBuilder<TButton> : IButtonBaseBuilder<TButton>
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase, new()
|
||||
{
|
||||
public TButton Button { get; } = new();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal static class ButtonBaseBuilderExtension
|
||||
{
|
||||
public static TBuilder SetContent<TBuilder, TButton>(this TBuilder builder, object? content)
|
||||
where TBuilder : IButtonBaseBuilder<TButton>
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
||||
{
|
||||
builder.Configure(builder => builder.Button.Content = content);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static TBuilder SetCommand<TBuilder, TButton>(this TBuilder builder, ICommand command)
|
||||
where TBuilder : IButtonBaseBuilder<TButton>
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
||||
{
|
||||
builder.Configure(builder => builder.Button.Command = command);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal sealed class ButtonBuilder : ButtonBaseBuilder<Button>;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal static class ButtonBuilderExtension
|
||||
{
|
||||
public static ButtonBuilder SetContent(this ButtonBuilder builder, object? content)
|
||||
{
|
||||
return builder.SetContent<ButtonBuilder, Button>(content);
|
||||
}
|
||||
|
||||
public static ButtonBuilder SetCommand(this ButtonBuilder builder, ICommand command)
|
||||
{
|
||||
return builder.SetCommand<ButtonBuilder, Button>(command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal interface IButtonBaseBuilder<TButton> : IBuilder
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
||||
{
|
||||
TButton Button { get; }
|
||||
}
|
||||
@@ -30,7 +30,7 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
|
||||
private WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>? sourceWeakEventListener;
|
||||
|
||||
public AdvancedCollectionView()
|
||||
: this(new List<T>(0))
|
||||
: this([])
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Windows.Foundation.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.Alternating;
|
||||
|
||||
[Obsolete("Use SettingsCard instead")]
|
||||
[DependencyProperty("ItemAlternateBackground", typeof(Microsoft.UI.Xaml.Media.Brush))]
|
||||
internal sealed partial class AlternatingItemsControl : ItemsControl
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.Alternating;
|
||||
|
||||
[Obsolete("Use SettingsCard instead")]
|
||||
internal interface IAlternatingItem
|
||||
{
|
||||
public Microsoft.UI.Xaml.Media.Brush? Background { get; set; }
|
||||
|
||||
@@ -28,4 +28,21 @@ internal static class FrameworkElementExtension
|
||||
frameworkElement.IsRightTapEnabled = false;
|
||||
frameworkElement.IsTabStop = false;
|
||||
}
|
||||
|
||||
public static void InitializeDataContext<TDataContext>(this FrameworkElement frameworkElement, IServiceProvider? serviceProvider = default)
|
||||
where TDataContext : class
|
||||
{
|
||||
IServiceProvider service = serviceProvider ?? Ioc.Default;
|
||||
try
|
||||
{
|
||||
frameworkElement.DataContext = service.GetRequiredService<TDataContext>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
ILogger? logger = service.GetRequiredService(typeof(ILogger<>).MakeGenericType([frameworkElement.GetType()])) as ILogger;
|
||||
logger?.LogError(ex, "Failed to initialize DataContext");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ internal sealed class CachedImage : Implementation.ImageEx
|
||||
/// </summary>
|
||||
public CachedImage()
|
||||
{
|
||||
DefaultStyleKey = typeof(CachedImage);
|
||||
DefaultStyleResourceUri = "ms-appx:///Control/Image/CachedImage.xaml".ToUri();
|
||||
|
||||
IsCacheEnabled = true;
|
||||
EnableLazyLoading = false;
|
||||
}
|
||||
@@ -32,7 +35,7 @@ internal sealed class CachedImage : Implementation.ImageEx
|
||||
|
||||
try
|
||||
{
|
||||
HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), HutaoExceptionKind.ImageCacheInvalidUri, SH.ControlImageCachedImageInvalidResourceUri);
|
||||
HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri);
|
||||
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
|
||||
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
|
||||
return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
|
||||
public Loading()
|
||||
{
|
||||
DefaultStyleKey = typeof(Loading);
|
||||
DefaultStyleResourceUri = new("ms-appx:///Control/Loading.xaml");
|
||||
DefaultStyleResourceUri = "ms-appx:///Control/Loading.xaml".ToUri();
|
||||
}
|
||||
|
||||
public bool IsLoading
|
||||
|
||||
@@ -12,7 +12,6 @@ internal sealed class UInt32Extension : MarkupExtension
|
||||
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
_ = uint.TryParse(Value, out uint result);
|
||||
return result;
|
||||
return XamlBindingHelper.ConvertValue(typeof(uint), Value);
|
||||
}
|
||||
}
|
||||
@@ -39,24 +39,6 @@ internal static class SoftwareBitmapExtension
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe double Luminance(this SoftwareBitmap softwareBitmap)
|
||||
{
|
||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||
{
|
||||
using (IMemoryBufferReference reference = buffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
|
||||
double sum = 0;
|
||||
foreach (ref readonly Bgra32 pixel in bytes)
|
||||
{
|
||||
sum += pixel.Luminance;
|
||||
}
|
||||
|
||||
return sum / bytes.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap)
|
||||
{
|
||||
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
|
||||
|
||||
@@ -82,8 +82,8 @@ internal partial class EqualPanel : Microsoft.UI.Xaml.Controls.Panel
|
||||
(d as EqualPanel)?.InvalidateMeasure();
|
||||
}
|
||||
|
||||
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
|
||||
private static void OnHorizontalAlignmentChanged(DependencyObject d, DependencyProperty dp)
|
||||
{
|
||||
InvalidateMeasure();
|
||||
(d as EqualPanel)?.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
@@ -18,13 +19,14 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
foreach (UIElement child in Children)
|
||||
List<UIElement> visibleChildren = Children.Where(child => child.Visibility is Visibility.Visible).ToList();
|
||||
foreach (ref readonly UIElement visibleChild in CollectionsMarshal.AsSpan(visibleChildren))
|
||||
{
|
||||
// ScrollViewer will always return an Infinity Size, we should use ActualWidth for this situation.
|
||||
double availableWidth = double.IsInfinity(availableSize.Width) ? ActualWidth : availableSize.Width;
|
||||
double childAvailableWidth = (availableWidth + Spacing) / Children.Count;
|
||||
double childAvailableWidth = (availableWidth + Spacing) / visibleChildren.Count;
|
||||
double childMaxAvailableWidth = Math.Max(MinItemWidth, childAvailableWidth);
|
||||
child.Measure(new(childMaxAvailableWidth - Spacing, ActualHeight));
|
||||
visibleChild.Measure(new(childMaxAvailableWidth - Spacing, ActualHeight));
|
||||
}
|
||||
|
||||
return base.MeasureOverride(availableSize);
|
||||
@@ -32,27 +34,29 @@ internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
int itemCount = Children.Count;
|
||||
double availableWidthPerItem = (finalSize.Width - (Spacing * (itemCount - 1))) / itemCount;
|
||||
double actualItemWidth = Math.Max(MinItemWidth, availableWidthPerItem);
|
||||
List<UIElement> visibleChildren = Children.Where(child => child.Visibility is Visibility.Visible).ToList();
|
||||
double availableItemWidth = (finalSize.Width - (Spacing * (visibleChildren.Count - 1))) / visibleChildren.Count;
|
||||
double actualItemWidth = Math.Max(MinItemWidth, availableItemWidth);
|
||||
|
||||
double offset = 0;
|
||||
foreach (UIElement child in Children)
|
||||
foreach (ref readonly UIElement visibleChild in CollectionsMarshal.AsSpan(visibleChildren))
|
||||
{
|
||||
child.Arrange(new Rect(offset, 0, actualItemWidth, finalSize.Height));
|
||||
visibleChild.Arrange(new Rect(offset, 0, actualItemWidth, finalSize.Height));
|
||||
offset += actualItemWidth + Spacing;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
private static void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MinWidth = (MinItemWidth * Children.Count) + (Spacing * (Children.Count - 1));
|
||||
HorizontalEqualPanel panel = (HorizontalEqualPanel)sender;
|
||||
int vivibleChildrenCount = panel.Children.Count(child => child.Visibility is Visibility.Visible);
|
||||
panel.MinWidth = (panel.MinItemWidth * vivibleChildrenCount) + (panel.Spacing * (vivibleChildrenCount - 1));
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
private static void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
InvalidateMeasure();
|
||||
((HorizontalEqualPanel)sender).InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,53 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
[DependencyProperty("MinItemWidth", typeof(double))]
|
||||
internal sealed partial class UniformPanel : UniformGrid
|
||||
[DependencyProperty("ColumnSpacing", typeof(double))]
|
||||
[DependencyProperty("RowSpacing", typeof(double))]
|
||||
internal sealed partial class UniformPanel : Microsoft.UI.Xaml.Controls.Panel
|
||||
{
|
||||
public UniformPanel()
|
||||
private int columns;
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
Columns = 1;
|
||||
SizeChanged += OnSizeChanged;
|
||||
columns = (int)((availableSize.Width + ColumnSpacing) / (MinItemWidth + ColumnSpacing));
|
||||
double availableItemWidth = ((availableSize.Width + ColumnSpacing) / columns) - ColumnSpacing;
|
||||
|
||||
double maxDesiredHeight = 0;
|
||||
foreach (UIElement child in Children)
|
||||
{
|
||||
child.Measure(new Size(availableItemWidth, availableSize.Height));
|
||||
maxDesiredHeight = Math.Max(maxDesiredHeight, child.DesiredSize.Height);
|
||||
}
|
||||
|
||||
int desiredRows = (int)Math.Ceiling(Children.Count / (double)columns);
|
||||
double desiredHeight = ((maxDesiredHeight + RowSpacing) * desiredRows) - RowSpacing;
|
||||
|
||||
return new Size(availableSize.Width, desiredHeight);
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
Columns = (int)((e.NewSize.Width + ColumnSpacing) / (MinItemWidth + ColumnSpacing));
|
||||
double itemWidth = ((finalSize.Width + ColumnSpacing) / columns) - ColumnSpacing;
|
||||
|
||||
for (int index = 0; index < Children.Count; index++)
|
||||
{
|
||||
UIElement child = Children[index];
|
||||
|
||||
int row = index / columns;
|
||||
int column = index % columns;
|
||||
|
||||
double x = column * (itemWidth + ColumnSpacing);
|
||||
double y = row * (child.DesiredSize.Height + RowSpacing);
|
||||
|
||||
child.Arrange(new Rect(x, y, itemWidth, child.DesiredSize.Height));
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ internal class ScopedPage : Page
|
||||
{
|
||||
private readonly RoutedEventHandler unloadEventHandler;
|
||||
private readonly CancellationTokenSource viewCancellationTokenSource = new();
|
||||
private readonly IServiceScope currentScope;
|
||||
private readonly IServiceScope pageScope;
|
||||
|
||||
private bool inFrame = true;
|
||||
|
||||
@@ -23,7 +23,7 @@ internal class ScopedPage : Page
|
||||
{
|
||||
unloadEventHandler = OnUnloaded;
|
||||
Unloaded += unloadEventHandler;
|
||||
currentScope = Ioc.Default.GetRequiredService<IScopedPageScopeReferenceTracker>().CreateScope();
|
||||
pageScope = Ioc.Default.GetRequiredService<IScopedPageScopeReferenceTracker>().CreateScope();
|
||||
}
|
||||
|
||||
public async ValueTask NotifyRecipientAsync(INavigationData extra)
|
||||
@@ -44,9 +44,17 @@ internal class ScopedPage : Page
|
||||
protected void InitializeWith<TViewModel>()
|
||||
where TViewModel : class, IViewModel
|
||||
{
|
||||
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
|
||||
viewModel.CancellationToken = viewCancellationTokenSource.Token;
|
||||
DataContext = viewModel;
|
||||
try
|
||||
{
|
||||
IViewModel viewModel = pageScope.ServiceProvider.GetRequiredService<TViewModel>();
|
||||
viewModel.CancellationToken = viewCancellationTokenSource.Token;
|
||||
DataContext = viewModel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
pageScope.ServiceProvider.GetRequiredService<ILogger<ScopedPage>>().LogError(ex, "Failed to initialize view model.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -95,7 +103,7 @@ internal class ScopedPage : Page
|
||||
viewModel.IsViewDisposed = true;
|
||||
|
||||
// Dispose the scope
|
||||
currentScope.Dispose();
|
||||
pageScope.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,15 +45,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
|
||||
|
||||
try
|
||||
{
|
||||
UpdateDescription(textBlock, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle((string)e.NewValue)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_ = ex;
|
||||
}
|
||||
UpdateDescription(textBlock, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle((string)e.NewValue)));
|
||||
}
|
||||
|
||||
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
|
||||
@@ -14,6 +14,7 @@ using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Text;
|
||||
|
||||
// TODO: change the parsing to syntax tree
|
||||
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
|
||||
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
|
||||
internal sealed partial class HtmlDescriptionTextBlock : ContentControl
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<x:String x:Key="UI_EmotionIcon25">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon25.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon52">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon52.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon71">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon71.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon89">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon89.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon250">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon272">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.png</x:String>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder;
|
||||
namespace Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
internal static class BuilderExtension
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
internal interface IBuilder;
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 可克隆
|
||||
/// </summary>
|
||||
/// <typeparam name="TSelf">自身类型</typeparam>
|
||||
[HighQuality]
|
||||
internal interface ICloneable<out TSelf>
|
||||
{
|
||||
/// <summary>
|
||||
/// 克隆
|
||||
/// </summary>
|
||||
/// <returns>新的克隆</returns>
|
||||
TSelf Clone();
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
|
||||
@@ -36,6 +39,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<ImageCache> logger;
|
||||
|
||||
@@ -104,12 +108,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
||||
{
|
||||
logger.LogDebug("Begin downloading image file from '{Uri}' to '{File}'", uri, filePath);
|
||||
logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (filePath, ConsoleColor.Cyan));
|
||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||
}
|
||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
{
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", uri);
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan));
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -132,10 +136,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
byte[] chars = Encoding.UTF8.GetBytes(url);
|
||||
byte[] hash = SHA1.HashData(chars);
|
||||
return System.Convert.ToHexString(hash);
|
||||
return Hash.SHA1HexString(uri.ToString());
|
||||
}
|
||||
|
||||
private static bool IsFileInvalid(string file, bool treatNullFileAsInvalid = true)
|
||||
@@ -172,38 +173,49 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
HttpRequestMessageBuilder requestMessageBuilder = httpRequestMessageBuilderFactory
|
||||
.Create()
|
||||
.SetRequestUri(uri)
|
||||
|
||||
if (message.IsSuccessStatusCode)
|
||||
// These headers are only available for our own api
|
||||
.SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase))
|
||||
.Get();
|
||||
|
||||
using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage)
|
||||
{
|
||||
using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile))
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
using (FileStream fileStream = File.Create(baseFile))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
switch (responseMessage.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Collection;
|
||||
|
||||
internal sealed class TwoEnumerbleEnumerator<TFirst, TSecond> : IDisposable
|
||||
{
|
||||
private readonly IEnumerator<TFirst> firstEnumerator;
|
||||
private readonly IEnumerator<TSecond> secondEnumerator;
|
||||
|
||||
public TwoEnumerbleEnumerator(IEnumerable<TFirst> firstEnumerable, IEnumerable<TSecond> secondEnumerable)
|
||||
{
|
||||
firstEnumerator = firstEnumerable.GetEnumerator();
|
||||
secondEnumerator = secondEnumerable.GetEnumerator();
|
||||
}
|
||||
|
||||
public (TFirst First, TSecond Second) Current { get => (firstEnumerator.Current, secondEnumerator.Current); }
|
||||
|
||||
public bool MoveNext(ref bool moveFirst, ref bool moveSecond)
|
||||
{
|
||||
moveFirst = moveFirst && firstEnumerator.MoveNext();
|
||||
moveSecond = moveSecond && secondEnumerator.MoveNext();
|
||||
|
||||
return moveFirst || moveSecond;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
firstEnumerator.Dispose();
|
||||
secondEnumerator.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,6 @@ namespace Snap.Hutao.Core.Database;
|
||||
[HighQuality]
|
||||
internal static class DbSetExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int AddAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -27,27 +20,13 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步添加并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
public static ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Add(entity);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加列表并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entities">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int AddRangeAndSave<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -55,27 +34,13 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步添加列表并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entities">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||
public static ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.AddRange(entities);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int RemoveAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -83,27 +48,13 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步移除并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
public static ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Remove(entity);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int UpdateAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -111,18 +62,11 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步更新并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
public static ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Update(entity);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -136,11 +80,11 @@ internal static class DbSetExtension
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static async ValueTask<int> SaveChangesAndClearChangeTrackerAsync<TEntity>(this DbSet<TEntity> dbSet)
|
||||
private static async ValueTask<int> SaveChangesAndClearChangeTrackerAsync<TEntity>(this DbSet<TEntity> dbSet, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
int count = await dbContext.SaveChangesAsync(token).ConfigureAwait(false);
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ internal static class DependencyInjection
|
||||
ServiceProvider serviceProvider = new ServiceCollection()
|
||||
|
||||
// Microsoft extension
|
||||
.AddLogging(builder => builder.AddDebug().AddConsoleWindow())
|
||||
.AddLogging(builder =>
|
||||
{
|
||||
builder
|
||||
.SetMinimumLevel(LogLevel.Trace)
|
||||
.AddDebug()
|
||||
.AddConsoleWindow();
|
||||
})
|
||||
.AddMemoryCache()
|
||||
|
||||
// Hutao extensions
|
||||
|
||||
@@ -33,17 +33,17 @@ internal static class IocConfiguration
|
||||
return services
|
||||
.AddTransient(typeof(Database.ScopedDbCurrent<,>))
|
||||
.AddTransient(typeof(Database.ScopedDbCurrent<,,>))
|
||||
.AddDbContext<AppDbContext>(AddDbContextCore);
|
||||
.AddDbContextPool<AppDbContext>(AddDbContextCore);
|
||||
}
|
||||
|
||||
private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder)
|
||||
private static void AddDbContextCore(IServiceProvider serviceProvider, DbContextOptionsBuilder builder)
|
||||
{
|
||||
RuntimeOptions runtimeOptions = provider.GetRequiredService<RuntimeOptions>();
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db");
|
||||
string sqlConnectionString = $"Data Source={dbFile}";
|
||||
|
||||
// Temporarily create a context
|
||||
using (AppDbContext context = AppDbContext.Create(sqlConnectionString))
|
||||
using (AppDbContext context = AppDbContext.Create(serviceProvider, sqlConnectionString))
|
||||
{
|
||||
if (context.Database.GetPendingMigrations().Any())
|
||||
{
|
||||
|
||||
@@ -18,13 +18,22 @@ internal static class ServiceProviderExtension
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDisposedSlow(this IServiceProvider? serviceProvider)
|
||||
public static bool IsDisposed(this IServiceProvider? serviceProvider)
|
||||
{
|
||||
if (serviceProvider is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (serviceProvider is ServiceProvider serviceProviderImpl)
|
||||
{
|
||||
return GetPrivateDisposed(serviceProviderImpl);
|
||||
}
|
||||
|
||||
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true;
|
||||
}
|
||||
|
||||
// private bool _disposed;
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")]
|
||||
private static extern ref bool GetPrivateDisposed(ServiceProvider serviceProvider);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
using Snap.Hutao.Core.Logging;
|
||||
|
||||
namespace Snap.Hutao.Core.Diagnostics;
|
||||
|
||||
internal readonly struct MeasureExecutionToken : IDisposable
|
||||
@@ -17,6 +19,6 @@ internal readonly struct MeasureExecutionToken : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
logger.LogColorizedDebug(("{Caller} toke {Time} ms", ConsoleColor.Gray), (callerName, ConsoleColor.Yellow), (stopwatch.GetElapsedTime().TotalMilliseconds, ConsoleColor.DarkGreen));
|
||||
}
|
||||
}
|
||||
@@ -5,50 +5,61 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
internal sealed class HutaoException : Exception
|
||||
{
|
||||
public HutaoException(HutaoExceptionKind kind, string message, Exception? innerException)
|
||||
: this(message, innerException)
|
||||
{
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
private HutaoException(string message, Exception? innerException)
|
||||
public HutaoException(string message, Exception? innerException)
|
||||
: base($"{message}\n{innerException?.Message}", innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public HutaoExceptionKind Kind { get; private set; }
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException Throw(HutaoExceptionKind kind, string message, Exception? innerException = default)
|
||||
public static HutaoException Throw(string message, Exception? innerException = default)
|
||||
{
|
||||
throw new HutaoException(kind, message, innerException);
|
||||
throw new HutaoException(message, innerException);
|
||||
}
|
||||
|
||||
public static void ThrowIf(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
|
||||
public static void ThrowIf(bool condition, string message, Exception? innerException = default)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
throw new HutaoException(kind, message, innerException);
|
||||
throw new HutaoException(message, innerException);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThrowIfNot(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
|
||||
public static void ThrowIfNot(bool condition, string message, Exception? innerException = default)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new HutaoException(kind, message, innerException);
|
||||
throw new HutaoException(message, innerException);
|
||||
}
|
||||
}
|
||||
|
||||
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
|
||||
{
|
||||
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
|
||||
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
|
||||
throw new HutaoException(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id), innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException UserdataCorrupted(string message, Exception? innerException = default)
|
||||
{
|
||||
throw new HutaoException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new InvalidCastException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static NotSupportedException NotSupported(string? message = default, Exception? innerException = default)
|
||||
{
|
||||
throw new NotSupportedException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? innerException = default)
|
||||
{
|
||||
return new OperationCanceledException(message, innerException);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ internal enum HutaoExceptionKind
|
||||
None,
|
||||
|
||||
// Foundation
|
||||
ServiceTypeCastFailed,
|
||||
ImageCacheInvalidUri,
|
||||
DatabaseCorrupted,
|
||||
UserdataCorrupted,
|
||||
|
||||
// IO
|
||||
FileSystemCreateFileInsufficientPermissions,
|
||||
@@ -18,4 +19,5 @@ internal enum HutaoExceptionKind
|
||||
// Service
|
||||
GachaStatisticsInvalidItemId,
|
||||
GameFpsUnlockingFailed,
|
||||
GameConfigInvalidChannelOptions,
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using Snap.Hutao.Win32.UI.Shell;
|
||||
using System.IO;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
using static Snap.Hutao.Win32.Ole32;
|
||||
using static Snap.Hutao.Win32.Shell32;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
@@ -39,4 +45,57 @@ internal static class FileOperation
|
||||
File.Move(sourceFileName, destFileName, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static unsafe bool UnsafeMove(string sourceFileName, string destFileName)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation)))
|
||||
{
|
||||
if (SUCCEEDED(SHCreateItemFromParsingName(sourceFileName, default, in IShellItem.IID, out IShellItem* pSourceShellItem)))
|
||||
{
|
||||
if (SUCCEEDED(SHCreateItemFromParsingName(destFileName, default, in IShellItem.IID, out IShellItem* pDestShellItem)))
|
||||
{
|
||||
pFileOperation->MoveItem(pSourceShellItem, pDestShellItem, default, default);
|
||||
|
||||
if (SUCCEEDED(pFileOperation->PerformOperations()))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
|
||||
pDestShellItem->Release();
|
||||
}
|
||||
|
||||
pSourceShellItem->Release();
|
||||
}
|
||||
|
||||
pFileOperation->Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static unsafe bool UnsafeDelete(string path)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (SUCCEEDED(CoCreateInstance(in Win32.UI.Shell.FileOperation.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileOperation.IID, out IFileOperation* pFileOperation)))
|
||||
{
|
||||
if (SUCCEEDED(SHCreateItemFromParsingName(path, default, in IShellItem.IID, out IShellItem* pShellItem)))
|
||||
{
|
||||
pFileOperation->DeleteItem(pShellItem, default);
|
||||
|
||||
if (SUCCEEDED(pFileOperation->PerformOperations()))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
|
||||
pShellItem->Release();
|
||||
}
|
||||
|
||||
pFileOperation->Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
20
src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs
Normal file
20
src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
internal static class Hash
|
||||
{
|
||||
public static string SHA1HexString(string input)
|
||||
{
|
||||
return HashCore(System.Convert.ToHexString, SHA1.HashData, Encoding.UTF8.GetBytes, input);
|
||||
}
|
||||
|
||||
private static TResult HashCore<TInput, TResult>(Func<byte[], TResult> resultConverter, Func<byte[], byte[]> hashMethod, Func<TInput, byte[]> bytesConverter, TInput input)
|
||||
{
|
||||
return resultConverter(hashMethod(bytesConverter(input)));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -77,34 +78,36 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent())
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
@@ -51,26 +52,30 @@ internal class StreamCopyWorker<TStatus>
|
||||
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
|
||||
do
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize))
|
||||
{
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
do
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
if (bytesRead is 0)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
while (bytesRead > 0);
|
||||
}
|
||||
while (bytesRead > 0);
|
||||
}
|
||||
}
|
||||
40
src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs
Normal file
40
src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
|
||||
internal sealed class StreamReaderWriter : IDisposable
|
||||
{
|
||||
private readonly StreamReader reader;
|
||||
private readonly StreamWriter writer;
|
||||
|
||||
public StreamReaderWriter(StreamReader reader, StreamWriter writer)
|
||||
{
|
||||
this.reader = reader;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public StreamReader Reader { get => reader; }
|
||||
|
||||
public StreamWriter Writer { get => writer; }
|
||||
|
||||
/// <inheritdoc cref="StreamReader.ReadLineAsync(CancellationToken)"/>
|
||||
public ValueTask<string?> ReadLineAsync(CancellationToken token)
|
||||
{
|
||||
return reader.ReadLineAsync(token);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="StreamWriter.WriteAsync(string?)"/>
|
||||
public Task WriteAsync(string value)
|
||||
{
|
||||
return writer.WriteAsync(value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
writer.Dispose();
|
||||
reader.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ internal readonly struct TempFile : IDisposable
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
HutaoException.Throw(HutaoExceptionKind.FileSystemCreateFileInsufficientPermissions, SH.CoreIOTempFileCreateFail, ex);
|
||||
HutaoException.Throw(SH.CoreIOTempFileCreateFail, ex);
|
||||
}
|
||||
|
||||
if (delete)
|
||||
|
||||
@@ -116,15 +116,15 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
// If it's the first time launch, we show the guide window anyway.
|
||||
// Otherwise, we check if there's any unfulfilled resource category present.
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
{
|
||||
if (StaticResource.IsAnyUnfulfilledCategoryPresent())
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
}
|
||||
}
|
||||
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
serviceProvider.GetRequiredService<GuideWindow>();
|
||||
|
||||
@@ -49,7 +49,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
||||
{
|
||||
byte[] content = new byte[header->ContentLength];
|
||||
serverStream.ReadAtLeast(content, header->ContentLength, false);
|
||||
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, HutaoExceptionKind.PrivateNamedPipeContentHashIncorrect, "PipePacket Content Hash incorrect");
|
||||
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
internal static class ConsoleVirtualTerminalSequences
|
||||
{
|
||||
public const string Default = "\u001b[0m";
|
||||
public const string Bold = "\u001b[1m";
|
||||
|
||||
public const string Underline = "\u001b[4m";
|
||||
|
||||
public const string Negative = "\u001b[7m";
|
||||
|
||||
public const string NoBold = "\u001b[22m";
|
||||
|
||||
public const string NoUnderline = "\u001b[24m";
|
||||
|
||||
public const string Positive = "\u001b[27m";
|
||||
|
||||
public const string ForegroundBlack = "\u001b[30m";
|
||||
public const string ForegroundRed = "\u001b[31m";
|
||||
public const string ForegroundGreen = "\u001b[32m";
|
||||
public const string ForegroundYellow = "\u001b[33m";
|
||||
public const string ForegroundBlue = "\u001b[34m";
|
||||
public const string ForegroundMagenta = "\u001b[35m";
|
||||
public const string ForegroundCyan = "\u001b[36m";
|
||||
public const string ForegroundWhite = "\u001b[37m";
|
||||
public const string ForegroundExtended = "\u001b[38m";
|
||||
public const string ForegroundDefault = "\u001b[39m";
|
||||
public const string BackgroundBlack = "\u001b[40m";
|
||||
public const string BackgroundRed = "\u001b[41m";
|
||||
public const string BackgroundGreen = "\u001b[42m";
|
||||
public const string BackgroundYellow = "\u001b[43m";
|
||||
public const string BackgroundBlue = "\u001b[44m";
|
||||
public const string BackgroundMagenta = "\u001b[45m";
|
||||
public const string BackgroundCyan = "\u001b[46m";
|
||||
public const string BackgroundWhite = "\u001b[47m";
|
||||
public const string BackgroundExtended = "\u001b[48m";
|
||||
public const string BackgroundDefault = "\u001b[49m";
|
||||
|
||||
public const string BrightForegroundBlack = "\u001b[1m\u001b[30m";
|
||||
public const string BrightForegroundRed = "\u001b[1m\u001b[31m";
|
||||
public const string BrightForegroundGreen = "\u001b[1m\u001b[32m";
|
||||
public const string BrightForegroundYellow = "\u001b[1m\u001b[33m";
|
||||
public const string BrightForegroundBlue = "\u001b[1m\u001b[34m";
|
||||
public const string BrightForegroundMagenta = "\u001b[1m\u001b[35m";
|
||||
public const string BrightForegroundCyan = "\u001b[1m\u001b[36m";
|
||||
public const string BrightForegroundWhite = "\u001b[1m\u001b[37m";
|
||||
public const string BrightBackgroundBlack = "\u001b[1m\u001b[40m";
|
||||
public const string BrightBackgroundRed = "\u001b[1m\u001b[41m";
|
||||
public const string BrightBackgroundGreen = "\u001b[1m\u001b[42m";
|
||||
public const string BrightBackgroundYellow = "\u001b[1m\u001b[43m";
|
||||
public const string BrightBackgroundBlue = "\u001b[1m\u001b[44m";
|
||||
public const string BrightBackgroundMagenta = "\u001b[1m\u001b[45m";
|
||||
public const string BrightBackgroundCyan = "\u001b[1m\u001b[46m";
|
||||
public const string BrightBackgroundWhite = "\u001b[1m\u001b[47m";
|
||||
|
||||
public const string Dim = "\u001b[2m";
|
||||
public const string Italic = "\u001b[3m";
|
||||
|
||||
public const string Blink = "\u001b[5m";
|
||||
|
||||
public const string Hidden = "\u001b[8m";
|
||||
public const string StrikeThrough = "\u001b[9m";
|
||||
public const string DoubleUnderline = "\u001b[21m";
|
||||
|
||||
public const string NoItalic = "\u001b[23m";
|
||||
|
||||
public const string NoBlink = "\u001b[25m";
|
||||
|
||||
public const string NoHidden = "\u001b[28m";
|
||||
public const string NoStrikeThrough = "\u001b[29m";
|
||||
|
||||
public static string FromConsoleColor(ConsoleColor color, bool foreground)
|
||||
{
|
||||
return (foreground, color) switch
|
||||
{
|
||||
(true, ConsoleColor.Black) => ForegroundBlack,
|
||||
(true, ConsoleColor.DarkBlue) => ForegroundBlue,
|
||||
(true, ConsoleColor.DarkGreen) => ForegroundGreen,
|
||||
(true, ConsoleColor.DarkCyan) => ForegroundCyan,
|
||||
(true, ConsoleColor.DarkRed) => ForegroundRed,
|
||||
(true, ConsoleColor.DarkMagenta) => ForegroundMagenta,
|
||||
(true, ConsoleColor.DarkYellow) => ForegroundYellow,
|
||||
(true, ConsoleColor.DarkGray) => BrightForegroundBlack,
|
||||
(true, ConsoleColor.Gray) => ForegroundWhite,
|
||||
(true, ConsoleColor.Blue) => BrightForegroundBlue,
|
||||
(true, ConsoleColor.Green) => BrightForegroundGreen,
|
||||
(true, ConsoleColor.Cyan) => BrightForegroundCyan,
|
||||
(true, ConsoleColor.Red) => BrightForegroundRed,
|
||||
(true, ConsoleColor.Magenta) => BrightForegroundMagenta,
|
||||
(true, ConsoleColor.Yellow) => BrightForegroundYellow,
|
||||
(true, ConsoleColor.White) => BrightForegroundWhite,
|
||||
(false, ConsoleColor.Black) => BackgroundBlack,
|
||||
(false, ConsoleColor.DarkBlue) => BackgroundBlue,
|
||||
(false, ConsoleColor.DarkGreen) => BackgroundGreen,
|
||||
(false, ConsoleColor.DarkCyan) => BackgroundCyan,
|
||||
(false, ConsoleColor.DarkRed) => BackgroundRed,
|
||||
(false, ConsoleColor.DarkMagenta) => BackgroundMagenta,
|
||||
(false, ConsoleColor.DarkYellow) => BackgroundYellow,
|
||||
(false, ConsoleColor.DarkGray) => BrightBackgroundBlack,
|
||||
(false, ConsoleColor.Gray) => BackgroundWhite,
|
||||
(false, ConsoleColor.Blue) => BrightBackgroundBlue,
|
||||
(false, ConsoleColor.Green) => BrightBackgroundGreen,
|
||||
(false, ConsoleColor.Cyan) => BrightBackgroundCyan,
|
||||
(false, ConsoleColor.Red) => BrightBackgroundRed,
|
||||
(false, ConsoleColor.Magenta) => BrightBackgroundMagenta,
|
||||
(false, ConsoleColor.Yellow) => BrightBackgroundYellow,
|
||||
(false, ConsoleColor.White) => BrightBackgroundWhite,
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ internal sealed class ConsoleWindowLifeTime : IDisposable
|
||||
HANDLE inputHandle = GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
|
||||
if (GetConsoleMode(inputHandle, out CONSOLE_MODE mode))
|
||||
{
|
||||
mode &= ~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE;
|
||||
mode &= ~CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(inputHandle, mode);
|
||||
}
|
||||
|
||||
@@ -38,4 +38,4 @@ internal sealed class ConsoleWindowLifeTime : IDisposable
|
||||
FreeConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
src/Snap.Hutao/Snap.Hutao/Core/Logging/LogArgument.cs
Normal file
33
src/Snap.Hutao/Snap.Hutao/Core/Logging/LogArgument.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
internal readonly struct LogArgument
|
||||
{
|
||||
public readonly object? Argument;
|
||||
public readonly ConsoleColor? ForegroundColor;
|
||||
public readonly ConsoleColor? BackgroundColor;
|
||||
|
||||
public LogArgument(object? argument, ConsoleColor? foreground = default, ConsoleColor? background = default)
|
||||
{
|
||||
Argument = argument;
|
||||
ForegroundColor = foreground;
|
||||
BackgroundColor = background;
|
||||
}
|
||||
|
||||
public static implicit operator LogArgument(string argument)
|
||||
{
|
||||
return new(argument);
|
||||
}
|
||||
|
||||
public static implicit operator LogArgument(double argument)
|
||||
{
|
||||
return new(argument);
|
||||
}
|
||||
|
||||
public static implicit operator LogArgument((object? Argument, ConsoleColor Foreground) tuple)
|
||||
{
|
||||
return new(tuple.Argument, tuple.Foreground);
|
||||
}
|
||||
}
|
||||
28
src/Snap.Hutao/Snap.Hutao/Core/Logging/LogMessage.cs
Normal file
28
src/Snap.Hutao/Snap.Hutao/Core/Logging/LogMessage.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
internal readonly struct LogMessage
|
||||
{
|
||||
public readonly string Message;
|
||||
public readonly ConsoleColor? ForegroundColor;
|
||||
public readonly ConsoleColor? BackgroundColor;
|
||||
|
||||
public LogMessage(string message, ConsoleColor? foreground = default, ConsoleColor? background = default)
|
||||
{
|
||||
Message = message;
|
||||
ForegroundColor = foreground;
|
||||
BackgroundColor = background;
|
||||
}
|
||||
|
||||
public static implicit operator LogMessage(string value)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static implicit operator LogMessage((string Value, ConsoleColor? Foreground) tuple)
|
||||
{
|
||||
return new(tuple.Value, tuple.Foreground);
|
||||
}
|
||||
}
|
||||
172
src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerExtension.cs
Normal file
172
src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerExtension.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
internal static class LoggerExtension
|
||||
{
|
||||
public static void LogColorizedDebug(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Debug, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedDebug(this ILogger logger, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Debug, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedTrace(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Trace, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedTrace(this ILogger logger, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Trace, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedInformation(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Information, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedInformation(this ILogger logger, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Information, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedWarning(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Warning, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedWarning(this ILogger logger, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Warning, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedError(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Error, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedError(this ILogger logger, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Error, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedCritical(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Critical, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorizedCritical(this ILogger logger, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(LogLevel.Critical, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorized(this ILogger logger, LogLevel logLevel, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(logLevel, 0, null, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorized(this ILogger logger, LogLevel logLevel, EventId eventId, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(logLevel, eventId, null, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorized(this ILogger logger, LogLevel logLevel, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
logger.LogColorized(logLevel, 0, exception, message, args);
|
||||
}
|
||||
|
||||
public static void LogColorized(this ILogger logger, LogLevel logLevel, EventId eventId, Exception? exception, LogMessage message, params LogArgument[] args)
|
||||
{
|
||||
string colorizedMessage = Colorize(message, args, out object?[] outArgs)!;
|
||||
logger.Log(logLevel, eventId, exception, colorizedMessage, outArgs);
|
||||
}
|
||||
|
||||
private static string? Colorize(LogMessage message, LogArgument[] args, out object?[] outArgs)
|
||||
{
|
||||
StringBuilder resultMessageBuilder = new(message.Message.Length);
|
||||
ReadOnlySpan<char> messageSpan = message.Message.AsSpan();
|
||||
|
||||
// Message base colors
|
||||
ConsoleColor? messageForeground = message.ForegroundColor;
|
||||
ConsoleColor? messageBackground = message.BackgroundColor;
|
||||
|
||||
if (messageForeground.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageForeground.Value, true));
|
||||
}
|
||||
|
||||
if (messageBackground.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageBackground.Value, false));
|
||||
}
|
||||
|
||||
ReadOnlySpan<LogArgument> argSpan = args.AsSpan();
|
||||
outArgs = new object?[args.Length];
|
||||
|
||||
int argIndex = 0;
|
||||
for (int index = 0; index < messageSpan.Length; index++)
|
||||
{
|
||||
if (messageSpan[index] == '{')
|
||||
{
|
||||
ref readonly LogArgument arg = ref argSpan[argIndex];
|
||||
outArgs[argIndex] = arg.Argument;
|
||||
argIndex++;
|
||||
if (arg.ForegroundColor.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(arg.ForegroundColor.Value, true));
|
||||
}
|
||||
|
||||
if (arg.BackgroundColor.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(arg.BackgroundColor.Value, false));
|
||||
}
|
||||
|
||||
int closingIndex = messageSpan[index..].IndexOf('}');
|
||||
resultMessageBuilder.Append(messageSpan.Slice(index, closingIndex + 1));
|
||||
|
||||
index += closingIndex;
|
||||
|
||||
if (arg.ForegroundColor.HasValue || arg.BackgroundColor.HasValue)
|
||||
{
|
||||
// Restore message colors
|
||||
if (messageForeground.HasValue || messageBackground.HasValue)
|
||||
{
|
||||
if (messageForeground.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageForeground.Value, true));
|
||||
}
|
||||
|
||||
if (messageBackground.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageBackground.Value, false));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultMessageBuilder.Append(messageSpan[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore default colors
|
||||
if (message.ForegroundColor.HasValue || message.BackgroundColor.HasValue)
|
||||
{
|
||||
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.Default);
|
||||
}
|
||||
|
||||
return resultMessageBuilder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,17 @@
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
internal static class LoggerFactoryExtensions
|
||||
internal static class LoggerFactoryExtension
|
||||
{
|
||||
public static ILoggingBuilder AddConsoleWindow(this ILoggingBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<ConsoleWindowLifeTime>();
|
||||
|
||||
builder.AddSimpleConsole();
|
||||
builder.AddSimpleConsole(options =>
|
||||
{
|
||||
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff ";
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,15 @@ internal static class RuntimeOptionsExtension
|
||||
|
||||
public static string GetDataFolderServerCacheFolder(this RuntimeOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "ServerCache");
|
||||
string directory = Path.Combine(options.DataFolder, "ServerCache");
|
||||
Directory.CreateDirectory(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
public static string GetDataFolderBackgroundFolder(this RuntimeOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "Background");
|
||||
string directory = Path.Combine(options.DataFolder, "Background");
|
||||
Directory.CreateDirectory(directory);
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ internal static class SettingKeys
|
||||
#region Application
|
||||
public const string LaunchTimes = "LaunchTimes";
|
||||
public const string DataFolderPath = "DataFolderPath";
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
public const string Major1Minor10Revision0GuideState = "Major1Minor10Revision0GuideState1";
|
||||
public const string StaticResourceImageQuality = "StaticResourceImageQuality";
|
||||
public const string StaticResourceImageArchive = "StaticResourceImageArchive";
|
||||
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
|
||||
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
|
||||
#endregion
|
||||
@@ -60,6 +62,10 @@ internal static class SettingKeys
|
||||
#endregion
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("重置新手引导状态")]
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
|
||||
[Obsolete("重置调试控制台开关")]
|
||||
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
|
||||
#endregion
|
||||
|
||||
@@ -21,22 +21,27 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
|
||||
public async ValueTask<bool> TryCreateDesktopShoutcutForElevatedLaunchAsync()
|
||||
{
|
||||
string targetLogoPath = Path.Combine(runtimeOptions.DataFolder, "ShellLinkLogo.ico");
|
||||
string elevatedLauncherPath = Path.Combine(runtimeOptions.DataFolder, "Snap.Hutao.Elevated.Launcher.exe");
|
||||
|
||||
try
|
||||
{
|
||||
Uri sourceLogoUri = "ms-appx:///Assets/Logo.ico".ToUri();
|
||||
StorageFile iconFile = await StorageFile.GetFileFromApplicationUriAsync(sourceLogoUri);
|
||||
await iconFile.OverwriteCopyAsync(targetLogoPath).ConfigureAwait(false);
|
||||
|
||||
Uri elevatedLauncherUri = "ms-appx:///Snap.Hutao.Elevated.Launcher.exe".ToUri();
|
||||
StorageFile launcherFile = await StorageFile.GetFileFromApplicationUriAsync(elevatedLauncherUri);
|
||||
await launcherFile.OverwriteCopyAsync(elevatedLauncherPath).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return UnsafeTryCreateDesktopShoutcutForElevatedLaunch(targetLogoPath);
|
||||
return UnsafeTryCreateDesktopShoutcutForElevatedLaunch(targetLogoPath, elevatedLauncherPath);
|
||||
}
|
||||
|
||||
private unsafe bool UnsafeTryCreateDesktopShoutcutForElevatedLaunch(string targetLogoPath)
|
||||
private unsafe bool UnsafeTryCreateDesktopShoutcutForElevatedLaunch(string targetLogoPath, string elevatedLauncherPath)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
@@ -44,17 +49,11 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
|
||||
HRESULT hr = CoCreateInstance(in ShellLink.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IShellLinkW.IID, out IShellLinkW* pShellLink);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
pShellLink->SetPath($"shell:AppsFolder\\{runtimeOptions.FamilyName}!App");
|
||||
pShellLink->SetPath(elevatedLauncherPath);
|
||||
pShellLink->SetArguments(runtimeOptions.FamilyName);
|
||||
pShellLink->SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL);
|
||||
pShellLink->SetIconLocation(targetLogoPath, 0);
|
||||
|
||||
if (SUCCEEDED(pShellLink->QueryInterface(in IShellLinkDataList.IID, out IShellLinkDataList* pShellLinkDataList)))
|
||||
{
|
||||
pShellLinkDataList->GetFlags(out uint flags);
|
||||
pShellLinkDataList->SetFlags(flags | (uint)SHELL_LINK_DATA_FLAGS.SLDF_RUNAS_USER);
|
||||
pShellLinkDataList->Release();
|
||||
}
|
||||
|
||||
if (SUCCEEDED(pShellLink->QueryInterface(in IPersistFile.IID, out IPersistFile* pPersistFile)))
|
||||
{
|
||||
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
|
||||
@@ -48,6 +48,7 @@ internal sealed class WindowSubclass : IDisposable
|
||||
{
|
||||
windowProc = OnSubclassProcedure;
|
||||
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0);
|
||||
bool propHooked = SetPropW(options.Hwnd, "NonRudeHWND", BOOL.TRUE);
|
||||
hotKeyController.RegisterAll();
|
||||
|
||||
bool titleBarHooked = true;
|
||||
@@ -55,7 +56,7 @@ internal sealed class WindowSubclass : IDisposable
|
||||
// only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar
|
||||
if (!options.UseLegacyDragBarImplementation)
|
||||
{
|
||||
return windowHooked && titleBarHooked;
|
||||
return windowHooked && propHooked && titleBarHooked;
|
||||
}
|
||||
|
||||
titleBarHooked = false;
|
||||
@@ -63,13 +64,13 @@ internal sealed class WindowSubclass : IDisposable
|
||||
|
||||
if (hwndDragBar.IsNull)
|
||||
{
|
||||
return windowHooked && titleBarHooked;
|
||||
return windowHooked && propHooked && titleBarHooked;
|
||||
}
|
||||
|
||||
legacyDragBarProc = OnLegacyDragBarProcedure;
|
||||
titleBarHooked = SetWindowSubclass(hwndDragBar, legacyDragBarProc, DragBarSubclassId, 0);
|
||||
|
||||
return windowHooked && titleBarHooked;
|
||||
return windowHooked && propHooked && titleBarHooked;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -78,6 +79,7 @@ internal sealed class WindowSubclass : IDisposable
|
||||
hotKeyController.UnregisterAll();
|
||||
|
||||
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
|
||||
RemovePropW(options.Hwnd, "NonRudeHWND");
|
||||
windowProc = default!;
|
||||
|
||||
if (options.UseLegacyDragBarImplementation)
|
||||
|
||||
@@ -118,14 +118,6 @@ internal static partial class EnumerableExtension
|
||||
collection.RemoveAt(collection.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到新类型的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">原始类型</typeparam>
|
||||
/// <typeparam name="TResult">新类型</typeparam>
|
||||
/// <param name="list">列表</param>
|
||||
/// <param name="selector">选择器</param>
|
||||
/// <returns>新类型的列表</returns>
|
||||
[Pure]
|
||||
public static List<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, TResult> selector)
|
||||
{
|
||||
@@ -200,6 +192,13 @@ internal static partial class EnumerableExtension
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static List<TSource> SortBy<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
|
||||
{
|
||||
list.Sort((left, right) => comparer.Compare(keySelector(left), keySelector(right)));
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static List<TSource> SortByDescending<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
|
||||
where TKey : IComparable
|
||||
@@ -207,4 +206,11 @@ internal static partial class EnumerableExtension
|
||||
list.Sort((left, right) => keySelector(right).CompareTo(keySelector(left)));
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static List<TSource> SortByDescending<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
|
||||
{
|
||||
list.Sort((left, right) => comparer.Compare(keySelector(right), keySelector(left)));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:shvg="using:Snap.Hutao.View.Guide"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid x:Name="RootGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
|
||||
@@ -14,10 +14,10 @@ namespace Snap.Hutao;
|
||||
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 1000;
|
||||
private const int MinHeight = 600;
|
||||
private const int MinHeight = 650;
|
||||
|
||||
private const int MaxWidth = 1200;
|
||||
private const int MaxHeight = 750;
|
||||
private const int MaxHeight = 800;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
|
||||
|
||||
@@ -27,4 +27,27 @@ internal sealed partial class IdentifyMonitorWindow : Window
|
||||
}
|
||||
|
||||
public string Monitor { get; private set; }
|
||||
|
||||
public static async ValueTask IdentifyAllMonitorsAsync(int secondsDelay)
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(secondsDelay).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.ViewModel.Game;
|
||||
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
|
||||
@@ -35,7 +36,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
|
||||
scope = serviceProvider.CreateScope();
|
||||
windowOptions = new(this, DragableGrid, new(MaxWidth, MaxHeight));
|
||||
this.InitializeController(serviceProvider);
|
||||
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService<LaunchGameViewModel>();
|
||||
RootGrid.InitializeDataContext<LaunchGameViewModel>(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
internal interface IAppDbEntity
|
||||
{
|
||||
Guid InnerId { get; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
internal interface IAppDbEntityHasArchive : IAppDbEntity
|
||||
{
|
||||
Guid ArchiveId { get; }
|
||||
}
|
||||
@@ -16,8 +16,8 @@ namespace Snap.Hutao.Model.Entity;
|
||||
[HighQuality]
|
||||
[Table("achievements")]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class Achievement
|
||||
: IEquatable<Achievement>,
|
||||
internal sealed class Achievement : IAppDbEntityHasArchive,
|
||||
IEquatable<Achievement>,
|
||||
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Model.Entity.Configuration;
|
||||
using System.Diagnostics;
|
||||
|
||||
@@ -23,18 +25,8 @@ internal sealed class AppDbContext : DbContext
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的应用程序数据库上下文
|
||||
/// </summary>
|
||||
/// <param name="options">选项</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options, ILogger<AppDbContext> logger)
|
||||
: this(options)
|
||||
{
|
||||
this.logger = logger;
|
||||
logger.LogInformation("{Name}[{Id}] created", nameof(AppDbContext), ContextId);
|
||||
logger = this.GetService<ILogger<AppDbContext>>();
|
||||
logger?.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("created", ConsoleColor.Green));
|
||||
}
|
||||
|
||||
public DbSet<SettingEntry> Settings { get; set; } = default!;
|
||||
@@ -73,21 +65,21 @@ internal sealed class AppDbContext : DbContext
|
||||
|
||||
public DbSet<SpiralAbyssEntry> SpiralAbysses { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的应用程序数据库上下文
|
||||
/// </summary>
|
||||
/// <param name="sqlConnectionString">连接字符串</param>
|
||||
/// <returns>应用程序数据库上下文</returns>
|
||||
public static AppDbContext Create(string sqlConnectionString)
|
||||
public static AppDbContext Create(IServiceProvider serviceProvider, string sqlConnectionString)
|
||||
{
|
||||
return new(new DbContextOptionsBuilder<AppDbContext>().UseSqlite(sqlConnectionString).Options);
|
||||
DbContextOptions<AppDbContext> options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseApplicationServiceProvider(serviceProvider)
|
||||
.UseSqlite(sqlConnectionString)
|
||||
.Options;
|
||||
|
||||
return new(options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
logger?.LogInformation("{Name}[{Id}] disposed", nameof(AppDbContext), ContextId);
|
||||
logger?.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("disposed", ConsoleColor.Red));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -18,7 +18,7 @@ internal sealed class AppDbContextDesignTimeFactory : IDesignTimeDbContextFactor
|
||||
#if DEBUG
|
||||
// TODO: replace with your own database file path.
|
||||
string userdataDbName = @"D:\Hutao\Userdata.db";
|
||||
return AppDbContext.Create($"Data Source={userdataDbName}");
|
||||
return AppDbContext.Create(default!, $"Data Source={userdataDbName}");
|
||||
#else
|
||||
throw Must.NeverHappen();
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,7 @@ internal sealed partial class SettingEntry
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
public const string IsUnobtainedWishItemVisible = "IsUnobtainedWishItemVisible";
|
||||
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
|
||||
@@ -11,34 +11,26 @@ namespace Snap.Hutao.Model.Intrinsic.Frozen;
|
||||
[HighQuality]
|
||||
internal static class IntrinsicFrozen
|
||||
{
|
||||
/// <summary>
|
||||
/// 所属地区
|
||||
/// </summary>
|
||||
public static FrozenSet<string> AssociationTypes { get; } = Enum.GetValues<AssociationType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 武器类型
|
||||
/// </summary>
|
||||
public static FrozenSet<NameValue<AssociationType>> AssociationTypeNameValues { get; } = Enum.GetValues<AssociationType>().Select(e => new NameValue<AssociationType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
|
||||
|
||||
public static FrozenSet<string> WeaponTypes { get; } = Enum.GetValues<WeaponType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 物品类型
|
||||
/// </summary>
|
||||
public static FrozenSet<NameValue<WeaponType>> WeaponTypeNameValues { get; } = Enum.GetValues<WeaponType>().Select(e => new NameValue<WeaponType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
|
||||
|
||||
public static FrozenSet<string> ItemQualities { get; } = Enum.GetValues<QualityType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 身材类型
|
||||
/// </summary>
|
||||
public static FrozenSet<NameValue<QualityType>> ItemQualityNameValues { get; } = Enum.GetValues<QualityType>().Select(e => new NameValue<QualityType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
|
||||
|
||||
public static FrozenSet<string> BodyTypes { get; } = Enum.GetValues<BodyType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 战斗属性
|
||||
/// </summary>
|
||||
public static FrozenSet<NameValue<BodyType>> BodyTypeNameValues { get; } = Enum.GetValues<BodyType>().Select(e => new NameValue<BodyType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
|
||||
|
||||
public static FrozenSet<string> FightProperties { get; } = Enum.GetValues<FightProperty>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 元素名称
|
||||
/// </summary>
|
||||
public static FrozenSet<NameValue<FightProperty>> FightPropertyNameValues { get; } = Enum.GetValues<FightProperty>().Select(e => new NameValue<FightProperty>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
|
||||
|
||||
public static FrozenSet<string> ElementNames { get; } = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
SH.ModelIntrinsicElementNameFire,
|
||||
@@ -50,6 +42,17 @@ internal static class IntrinsicFrozen
|
||||
SH.ModelIntrinsicElementNameRock,
|
||||
]);
|
||||
|
||||
public static FrozenSet<NameValue<int>> ElementNameValues { get; } = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameFire, 1),
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameWater, 2),
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameGrass, 3),
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameElec, 4),
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameWind, 5),
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameIce, 6),
|
||||
new NameValue<int>(SH.ModelIntrinsicElementNameRock, 7),
|
||||
]);
|
||||
|
||||
public static FrozenSet<string> MaterialTypeDescriptions { get; } = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
SH.ModelMetadataMaterialCharacterAndWeaponEnhancementMaterial,
|
||||
|
||||
16
src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/QuestType.cs
Normal file
16
src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/QuestType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
internal enum QuestType
|
||||
{
|
||||
AQ,
|
||||
FQ,
|
||||
LQ,
|
||||
EQ,
|
||||
DQ,
|
||||
IQ,
|
||||
VQ,
|
||||
WQ,
|
||||
}
|
||||
16
src/Snap.Hutao/Snap.Hutao/Model/Metadata/City.cs
Normal file
16
src/Snap.Hutao/Snap.Hutao/Model/Metadata/City.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata;
|
||||
|
||||
// CityTaskOpenExcelConfig
|
||||
internal enum City : uint
|
||||
{
|
||||
Mondstadt = 1,
|
||||
Liyue = 2,
|
||||
Inazuma = 3,
|
||||
Sumeru = 4,
|
||||
Fontaine = 5,
|
||||
Natlan,
|
||||
Snezhnaya,
|
||||
}
|
||||
@@ -9,24 +9,6 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
internal sealed class AssociationTypeIconConverter : ValueConverter<AssociationType, Uri?>
|
||||
{
|
||||
private static readonly FrozenDictionary<string, AssociationType> LocalizedNameToAssociationType = FrozenDictionary.ToFrozenDictionary(
|
||||
[
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeMondstadt, AssociationType.ASSOC_TYPE_MONDSTADT),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeLiyue, AssociationType.ASSOC_TYPE_LIYUE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFatui, AssociationType.ASSOC_TYPE_FATUI),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeInazuma, AssociationType.ASSOC_TYPE_INAZUMA),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeRanger, AssociationType.ASSOC_TYPE_RANGER),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSumeru, AssociationType.ASSOC_TYPE_SUMERU),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFontaine, AssociationType.ASSOC_TYPE_FONTAINE),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeNatlan, AssociationType.ASSOC_TYPE_NATLAN),
|
||||
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSnezhnaya, AssociationType.ASSOC_TYPE_SNEZHNAYA),
|
||||
]);
|
||||
|
||||
public static Uri? AssociationTypeNameToIconUri(string associationTypeName)
|
||||
{
|
||||
return AssociationTypeToIconUri(LocalizedNameToAssociationType.GetValueOrDefault(associationTypeName));
|
||||
}
|
||||
|
||||
public static Uri? AssociationTypeToIconUri(AssociationType type)
|
||||
{
|
||||
string? association = type switch
|
||||
|
||||
@@ -11,11 +11,6 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
[HighQuality]
|
||||
internal sealed class AvatarNameCardPicConverter : ValueConverter<Avatar.Avatar?, Uri>
|
||||
{
|
||||
/// <summary>
|
||||
/// 从角色转换到名片
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <returns>名片</returns>
|
||||
public static Uri AvatarToUri(Avatar.Avatar? avatar)
|
||||
{
|
||||
if (avatar is null)
|
||||
|
||||
@@ -21,6 +21,7 @@ internal static class MonsterRelationship
|
||||
5112U => 511U, // 历经百战的浊水喷吐幻灵
|
||||
30605U => 30603U, // 历经百战的霜剑律从
|
||||
30606U => 30604U, // 历经百战的幽风铃兰
|
||||
40632U => 40613U, // 自律超算型场力发生装置
|
||||
60402U => 60401U, // (火)岩龙蜥
|
||||
60403U => 60401U, // (冰)岩龙蜥
|
||||
60404U => 60401U, // (雷)岩龙蜥
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.9.8.0" />
|
||||
Version="1.9.9.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutaoDev"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.9.8.0" />
|
||||
Version="1.9.9.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao Dev</DisplayName>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using WinRT;
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>No results found</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>Invalid Uri</value>
|
||||
</data>
|
||||
@@ -650,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>Open UIAF Json File</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<value>Multiple identical achievement IDs found in a single achievement archive</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Enter your HoYoLab UID</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>You are using an embedded webview to login to your MiHoYo passport account, the client will fetch the Cookie data when you click on I'm Signed in button. All network communication initialed in this webview occurs only between your computer and MiHoYo official servers.</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>I'm logged in</value>
|
||||
</data>
|
||||
@@ -2916,13 +2922,19 @@
|
||||
<value>Weapon WIKI</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
|
||||
<value>(?:〓Event Duration〓|〓Quest Start Time〓).*?\d\.\dthe Version update(?:after|)Permanently available</value>
|
||||
<value>(?:〓Event Duration〓|〓Quest Start Time〓).*?Permanently.*?Version.*?(\d\.\d).*?update</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
|
||||
<value>〓Event Duration〓.*?\d\.\d Available throughout the entirety of Version</value>
|
||||
<value>〓Event Duration〓.*?Available throughout the entirety of Version (\d\.\d)</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
|
||||
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】|〓Discount Period〓).*?(\d\.\dAfter the Version update).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】|〓Discount Period〓).*?After.*?(\d\.\d).*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
|
||||
<value>Dear.*?&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
|
||||
<value>Version \d\.\d Update Maintenance Preview</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
|
||||
<value>〓Update Maintenance Duration〓.+?&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>Simpan</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>Invalid Url</value>
|
||||
</data>
|
||||
@@ -650,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>Buka berkas UIAF Json</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<value>Terdapat beberapa ID pencapaian yang identik dalam satu arsip pencapaian</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Masukkan UID HoYoLab Anda</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>Saya sudah masuk</value>
|
||||
</data>
|
||||
@@ -2916,13 +2922,19 @@
|
||||
<value>Senjata WIKI</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
|
||||
<value>(?:〓Durasi Event〓|〓Waktu Mulai Misi〓).*?\d\.\dthe Version update(?:after|)Selamanya Tersedia</value>
|
||||
<value>(?:〓Durasi Event〓|〓Waktu Mulai Misi〓).*?(\d\.\d)the Version update(?:after|)Selamanya Tersedia</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
|
||||
<value>〓Durasi Event〓.*?\d\.\d Tersedia selama versi ini</value>
|
||||
<value>〓Durasi Event〓.*?(\d\.\d) Tersedia selama versi ini</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
|
||||
<value>(?:〓Waktu Acara〓|Waktu Menginginkan|【Waktu Peluncuran】|〓Waktu Diskon〓).*?(\d\.\d Setelah Pembaruan Versi).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
<value>(?:〓Waktu Acara〓|Waktu Menginginkan|【Waktu Peluncuran】|〓Waktu Diskon〓).*?(\d\.\d) Setelah Pembaruan Versi.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
|
||||
<value>将于&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;进行版本更新维护</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
|
||||
<value>\d\.\d版本更新维护预告</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
|
||||
<value>〓Durasi Pemeliharaan Pembaruan.+?&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>保存</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>無効なURL</value>
|
||||
</data>
|
||||
@@ -650,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>UIAF Json ファイルを開く</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<value>複数の同一アチーブメント Idがアーカイブに混在しています</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
@@ -1368,7 +1371,7 @@
|
||||
<value>クッキーを設定</value>
|
||||
</data>
|
||||
<data name="ViewFeedbackHeader" xml:space="preserve">
|
||||
<value>フィードバック センター</value>
|
||||
<value>フィードバックセンター</value>
|
||||
</data>
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>祈願履歴</value>
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>HoYoLab UIDを入力してください</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>ログインしました</value>
|
||||
</data>
|
||||
@@ -2505,7 +2511,7 @@
|
||||
<value>アチーブメント</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve">
|
||||
<value>リアルタイムノート</value>
|
||||
<value>リアルタイムノート</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardItemgachaStatisticsHeader" xml:space="preserve">
|
||||
<value>祈願履歴</value>
|
||||
@@ -2859,7 +2865,7 @@
|
||||
<value>QRコードでログイン</value>
|
||||
</data>
|
||||
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
|
||||
<value>手入力</value>
|
||||
<value>手動入力</value>
|
||||
</data>
|
||||
<data name="ViewUserCookieOperationRefreshCookieAction" xml:space="preserve">
|
||||
<value>Cookie 再取得</value>
|
||||
@@ -2916,19 +2922,25 @@
|
||||
<value>武器一覧</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
|
||||
<value>(?:〓イベント期間〓|〓任務開始時間〓).*?\d\.\dバージョンアップ(?:完了|)後常設オープン</value>
|
||||
<value>(?:〓イベント期間〓|〓任務開(?:始|放)時間〓).*?(\d\.\d).*?開放</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
|
||||
<value>〓イベント期間〓.*?\d\.\d当バージョン期間オープン</value>
|
||||
<value>〓イベント期間〓.*?(\d\.\d)バージョン</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
|
||||
<value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\dバージョンアップ完了後).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
<value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\d)バージョンアップ.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdatePreviewTime" xml:space="preserve">
|
||||
<value>親愛.*?&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdatePreviewTitle" xml:space="preserve">
|
||||
<value>Ver\.\d\.\dバージョンアップのお知らせ</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
|
||||
<value>〓メンテナンス時間〓.+?&lt;t class=\"t_(?:gl|lc)\".*?&gt;(.*?)&lt;/t&gt;</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve">
|
||||
<value>Ver.\d\.\d.+正式リリース</value>
|
||||
<value>Ver\.\d\.\d.*?正式リリース</value>
|
||||
</data>
|
||||
<data name="WebAnnouncementTimeDaysBeginFormat" xml:space="preserve">
|
||||
<value>{0} 日後に開始</value>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user