Compare commits

..

102 Commits

Author SHA1 Message Date
Lightczx
8703c3a598 init 2024-04-16 17:28:15 +08:00
Lightczx
80d6d5eb2b fix build 2024-04-16 15:53:41 +08:00
Lightczx
f682bb57e8 refactor achievement 2024-04-16 15:51:30 +08:00
Lightczx
486c6eb308 achievement service 2024-04-16 10:13:35 +08:00
Lightczx
0629f7c4c9 Introducing IAppInfrastructureService 2024-04-15 17:29:20 +08:00
Lightczx
b49288a98f Update DiscordController.cs 2024-04-15 13:49:30 +08:00
DismissedLight
ee99d0b665 refactor static resource 2024-04-12 21:40:00 +08:00
Lightczx
72b62aa9c6 ServerGarbageCollection 2024-04-12 17:30:10 +08:00
Lightczx
6b031e1866 update static resource setting 2024-04-12 16:52:54 +08:00
Lightczx
59c03c7f3b update WAS to 1.5.2 2024-04-12 11:57:49 +08:00
Lightczx
c03a96b44f refine guide ui 2024-04-11 16:14:04 +08:00
DismissedLight
d5a97903d3 Merge pull request #1545 from DGP-Studio/feat/version1.10guide 2024-04-11 15:57:44 +08:00
Lightczx
7f998dc87f impl #1430 2024-04-11 15:48:11 +08:00
Lightczx
2367c4759d refactor 2024-04-10 16:53:21 +08:00
DismissedLight
043e3f07d8 Merge pull request #1539 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-017b70aa21 2024-04-08 16:15:33 +08:00
dependabot[bot]
cd075c4dab Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx) and [MSTest.TestFramework](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.2 to 3.3.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.2...v3.3.1)

Updates `MSTest.TestFramework` from 3.2.2 to 3.3.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.2...v3.3.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 07:37:05 +00:00
Lightczx
65252f1f69 refactor launch game view model 2024-04-08 15:33:42 +08:00
DismissedLight
f5dd5f4c1d Merge pull request #1531 from DGP-Studio/feat/fix_config 2024-04-08 14:08:21 +08:00
Lightczx
27ce55f3f7 code style 2024-04-08 14:06:04 +08:00
qhy040404
311941bb89 code style 2024-04-08 14:01:59 +08:00
qhy040404
07e2489cab use header instead of another textblock 2024-04-08 12:11:46 +08:00
qhy040404
699ac60aaf apply suggestions 2024-04-08 12:00:16 +08:00
qhy040404
dc7bc7e35d fix compile error 2024-04-08 11:59:05 +08:00
qhy040404
bf67fcf3a2 add ability to fix missing config.ini 2024-04-08 11:59:05 +08:00
DismissedLight
5093246571 Merge pull request #1538 from DGP-Studio/feat/IFileOperation 2024-04-08 11:34:09 +08:00
Lightczx
94fe192581 add IFileOperation 2024-04-08 11:25:32 +08:00
Lightczx
c679032387 button builder 2024-04-08 09:40:53 +08:00
Lightczx
d119b056c7 refactor achievement viewmodel 2024-04-07 16:50:35 +08:00
Lightczx
ab95ce8ce8 code style 2024-04-07 15:07:53 +08:00
Lightczx
0ede5b158f button builder 2024-04-07 14:49:36 +08:00
Lightczx
6c9a98c2c9 refactor infobarservice 2024-04-07 13:30:38 +08:00
Lightczx
a725fc0e9e fix clamp upper bound 2024-04-07 10:46:09 +08:00
Lightczx
5251dd9343 clamp monitor index 2024-04-07 10:44:42 +08:00
Lightczx
0b71053bf9 backup&restore game config.ini 2024-04-02 16:55:53 +08:00
Lightczx
a91499171d update flow rework 2024-04-02 15:37:20 +08:00
Lightczx
e041def070 remove AddUserAsync 2024-04-01 16:50:33 +08:00
Masterain
2e81ddf3f4 在设置中增加低价原石购买站点链接
### Hello April 🎁
- Update Banner
2024-03-31 22:42:48 -07:00
DismissedLight
4d988e5500 Merge pull request #1527 from Masterain98/main
April Fools
2024-04-01 13:25:55 +08:00
Masterain
36aff679c0 Update Banner 2024-03-31 22:19:33 -07:00
Lightczx
d490393108 fix setting spacing 2024-03-29 16:52:37 +08:00
DismissedLight
1633f2c668 Merge pull request #1511 from DGP-Studio/readme2024 2024-03-29 15:48:37 +08:00
DismissedLight
241122eadc Merge pull request #1517 from DGP-Studio/develop 2024-03-29 15:47:39 +08:00
Masterain
339bb9292b Update SECURITY.md 2024-03-29 00:34:43 -07:00
DismissedLight
4e27d40b76 Merge pull request #1509 from DGP-Studio/l10n_develop 2024-03-29 15:29:48 +08:00
Masterain
4f42a299d1 New translations sh.resx (English) 2024-03-29 00:28:14 -07:00
Masterain
80aad5f09e New translations sh.resx (Indonesian) 2024-03-29 00:24:57 -07:00
Masterain
35370c392b New translations sh.resx (English) 2024-03-29 00:24:56 -07:00
Masterain
115dae8966 New translations sh.resx (Chinese Traditional) 2024-03-29 00:24:55 -07:00
Masterain
795e9730b9 New translations sh.resx (Russian) 2024-03-29 00:24:54 -07:00
Masterain
7dac0d6f73 New translations sh.resx (Portuguese) 2024-03-29 00:24:53 -07:00
Masterain
7c42b7e8ed New translations sh.resx (Korean) 2024-03-29 00:24:52 -07:00
Masterain
3c766f55b5 New translations sh.resx (Japanese) 2024-03-29 00:24:51 -07:00
Lightczx
38a07caeab update version 2024-03-29 15:22:49 +08:00
Lightczx
5fb55ea645 update desc 2024-03-29 15:19:43 +08:00
Masterain
aba568d8c6 New translations sh.resx (Indonesian) 2024-03-28 23:27:17 -07:00
Masterain
116bd58a81 New translations sh.resx (English) 2024-03-28 23:27:16 -07:00
Masterain
46cc5f7f1e New translations sh.resx (Chinese Traditional) 2024-03-28 23:27:15 -07:00
Masterain
3e5ca2440b New translations sh.resx (Russian) 2024-03-28 23:27:14 -07:00
Masterain
081a60f3f8 New translations sh.resx (Portuguese) 2024-03-28 23:27:13 -07:00
Masterain
4a5ddd872d New translations sh.resx (Korean) 2024-03-28 23:27:12 -07:00
Masterain
81e5159615 New translations sh.resx (Japanese) 2024-03-28 23:27:11 -07:00
Lightczx
d5eec58157 add hint for mihoyo passport login 2024-03-29 14:16:37 +08:00
Lightczx
0242d3d0ca fix #1504 2024-03-28 15:31:05 +08:00
Lightczx
3fcb4caaf1 wiki avatar title no wrap 2024-03-28 14:17:28 +08:00
DismissedLight
ae2ddee6c4 Merge pull request #1510 from GoddessLuBoYan/issue1427 2024-03-28 13:32:58 +08:00
Lightczx
089938c1c0 code style 2024-03-28 13:16:40 +08:00
Lightczx
c515f70e95 code style 2024-03-28 11:19:56 +08:00
Lightczx
317b68b1ce improve image cache 2024-03-28 11:18:09 +08:00
DismissedLight
7b5a6ccae0 Merge pull request #1513 from DGP-Studio/feat/cultivation_stat_finished 2024-03-28 11:00:33 +08:00
qhy040404
13108b6694 show finished icon if we have enough items 2024-03-27 16:23:30 +08:00
goddessluboyan
c4ff359995 修复多语言问题 2024-03-26 13:37:27 +08:00
goddessluboyan
70399f7a7d 删除调试用的信息 2024-03-26 13:37:27 +08:00
goddessluboyan
3d1a365079 实现对材料统计页面中的材料做初步排序。
更细致的排序、分类功能,需要集成观测枢功能才能进行。
2024-03-26 13:37:27 +08:00
Masterain
2b6d4af5a3 Update README.md 2024-03-25 17:48:41 -07:00
Masterain
80450660e4 Update README.md 2024-03-25 17:47:16 -07:00
Masterain
28b09d56f3 New translations sh.resx (Indonesian) 2024-03-25 07:59:18 -07:00
Masterain
995860463f New translations sh.resx (English) 2024-03-25 07:59:17 -07:00
Masterain
474d06457c New translations sh.resx (Chinese Traditional) 2024-03-25 07:59:16 -07:00
Masterain
7534729794 New translations sh.resx (Russian) 2024-03-25 07:59:15 -07:00
Masterain
167d66aa67 New translations sh.resx (Portuguese) 2024-03-25 07:59:14 -07:00
Masterain
e20a9f3ff4 New translations sh.resx (Korean) 2024-03-25 07:59:12 -07:00
Masterain
868232d98f New translations sh.resx (Japanese) 2024-03-25 07:59:11 -07:00
DismissedLight
ab1b3c02c8 Merge pull request #1500 from DGP-Studio/feat/suggestbox_not_found 2024-03-25 22:43:47 +08:00
DismissedLight
8ef5dc9302 code style 2024-03-25 22:41:58 +08:00
Masterain
4c7e02cd5c New translations sh.resx (Chinese Traditional) 2024-03-24 11:14:18 -07:00
Masterain
d8e0d74be6 New translations sh.resx (Chinese Traditional) 2024-03-24 10:09:39 -07:00
Masterain
655f97353c New translations sh.resx (Chinese Traditional) 2024-03-24 09:12:43 -07:00
qhy040404
a387e0dbf5 fix achievement search reset 2024-03-23 12:59:38 +08:00
qhy040404
8111c1e662 Disable unknown token 2024-03-23 12:58:49 +08:00
qhy040404
9cd9193425 show not found in view 2024-03-23 12:58:49 +08:00
qhy040404
d531c81fa2 show not found in search box 2024-03-23 12:58:49 +08:00
DismissedLight
1d0a72493e achievement city typedef 2024-03-21 22:27:16 +08:00
Lightczx
f2b361819b fix userview 2024-03-20 10:42:14 +08:00
qhy040404
41f7245a1a fix #1503 2024-03-19 22:46:58 +08:00
DismissedLight
889e914f7f colorized log message 2024-03-19 22:23:47 +08:00
Lightczx
9f90ec221c colorized 2024-03-19 17:30:23 +08:00
DismissedLight
8fc874fd09 Merge pull request #1498 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-f9ad2d8712 2024-03-19 15:13:47 +08:00
Lightczx
f42ec1ea12 change banner color 2024-03-18 17:25:45 +08:00
Lightczx
5cc3cf264c refactor 2024-03-18 17:15:42 +08:00
Lightczx
e38517a2ad optimize uniformpanel 2024-03-18 16:54:17 +08:00
dependabot[bot]
cdc0fb8a82 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [coverlet.collector](https://github.com/coverlet-coverage/coverlet).


Updates `coverlet.collector` from 6.0.1 to 6.0.2
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 07:31:21 +00:00
ema
db3a611c6e upgrade SH.ja.resx translation (#1491) 2024-03-16 14:54:23 +08:00
235 changed files with 4643 additions and 2550 deletions

View File

@@ -1,42 +1,36 @@
![HutaoRepoBanner2-20231222](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/2d178de1-95bc-44a1-a95e-20c5f11a8628)
![HutaoRepoBanner3-en](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/7289da68-59cf-409b-bd85-4b5a01d0c091)
胡桃工具箱是一款以 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://ci.appveyor.com/api/projects/status/n4s40t9llru4si9y?svg=true) [![GitHub Release](https://img.shields.io/github/release/DGP-Studio/Snap.Hutao?style=flat)](https://github.com/DGP-Studio/Snap.Hutao/releases/latest) [![Github All Releases](https://img.shields.io/github/downloads/DGP-Studio/Snap.Hutao/total.svg?style=flat)]()
---
#### 使用安装器安装 / 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)
![zh-TW translation](https://img.shields.io/badge/dynamic/json?color=blue&label=zh-TW&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27zh-TW%27)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ![en translation](https://img.shields.io/badge/dynamic/json?color=blue&label=en&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27en%27)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ![id translation](https://img.shields.io/badge/dynamic/json?color=blue&label=id&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27id%27)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ![ja translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ja&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27ja%27)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ![ko translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ko&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27ko%27)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)![pt-PT translation](https://img.shields.io/badge/dynamic/json?color=blue&label=pt-PT&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27pt-PT%27)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ![ru translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ru&style=flat&logo=crowdin&query=%24.progress[?(@.data.languageId==%27ru%27)].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)
[![Discord](https://img.shields.io/discord/952488447753465916?color=5865f2&label=%20Discord)](https://discord.gg/CcH5XtDtvR) [![QQ](https://img.shields.io/badge/QQ-EB1923?logo=tencent-qq&logoColor=white&label=567908135)](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

View File

@@ -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

Binary file not shown.

BIN
res/Banner3-large.psd Normal file

Binary file not shown.

BIN
res/Store/chs/abyss.psd Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
res/Store/chs/lancher.psd Normal file

Binary file not shown.

Binary file not shown.

BIN
res/Store/chs/wish.psd Normal file

Binary file not shown.

BIN
res/Store/en/abyss.psd Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
res/Store/en/lancher.psd Normal file

Binary file not shown.

Binary file not shown.

BIN
res/Store/en/wish.psd Normal file

Binary file not shown.

View File

@@ -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]
#### 命名样式 ####

View File

@@ -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; }

View File

@@ -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>

View File

@@ -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));
}
}

View File

@@ -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,34 +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);
}
if (this.FindDescendant("PART_AutoSuggestBox") is Microsoft.UI.Xaml.Controls.AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.GotFocus += OnSuggestBoxFocusGot;
autoSuggestBox.LosingFocus += OnSuggestBoxFocusLosing;
}
}
private void OnSuggestBoxFocusGot(object sender, RoutedEventArgs e)
{
if (sender is Microsoft.UI.Xaml.Controls.AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.ItemsSource = AvailableTokens.Values;
}
}
private void OnSuggestBoxFocusLosing(object sender, RoutedEventArgs e)
{
if (sender is Microsoft.UI.Xaml.Controls.AutoSuggestBox autoSuggestBox)
{
autoSuggestBox.ItemsSource = null;
CornerRadius overlayCornerRadius = appResourceProvider.GetResource<CornerRadius>("OverlayCornerRadius");
CornerRadiusFilterConverter cornerRadiusFilterConverter = new() { Filter = CornerRadiusFilterKind.Bottom };
border.CornerRadius = (CornerRadius)cornerRadiusFilterConverter.Convert(overlayCornerRadius, typeof(CornerRadius), default, default);
}
}
@@ -65,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);
}
}
@@ -91,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);
}
}

View File

@@ -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;

View File

@@ -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,
}

View File

@@ -33,6 +33,7 @@ internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavio
protected override bool Uninitialize()
{
periodicTimerCancellationTokenSource.Cancel();
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
return true;
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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>;

View File

@@ -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);
}
}

View File

@@ -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; }
}

View File

@@ -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([])
{
}

View File

@@ -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
{

View File

@@ -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; }

View File

@@ -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.

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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>

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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,
}

View 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(BitConverter.ToString, 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)));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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>();

View File

@@ -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;
}

View File

@@ -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,
};
}
}

View File

@@ -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();
}
}
}
}

View 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 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((object? Argument, ConsoleColor Foreground) tuple)
{
return new(tuple.Argument, tuple.Foreground);
}
}

View 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);
}
}

View 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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/>

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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>
{

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.Entity.Configuration;
using System.Diagnostics;
@@ -34,7 +35,7 @@ internal sealed class AppDbContext : DbContext
: this(options)
{
this.logger = logger;
logger.LogInformation("{Name}[{Id}] created", nameof(AppDbContext), ContextId);
logger.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("created", ConsoleColor.Green));
}
public DbSet<SettingEntry> Settings { get; set; } = default!;
@@ -87,7 +88,7 @@ internal sealed class AppDbContext : DbContext
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/>

View File

@@ -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,

View 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,
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">
@@ -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>

View File

@@ -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>잘못된 Uri</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">
@@ -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>

View File

@@ -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>Salvar</value>
</data>
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
<value>未找到结果</value>
</data>
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
<value>Uri inválido</value>
</data>
@@ -650,7 +653,7 @@
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
<value>Abrir arquivo Json UIAF</value>
</data>
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
<value>Várias IDs de conquistas idênticas encontradas em um único arquivo de conquistas</value>
</data>
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
@@ -2342,6 +2345,9 @@
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>Digite seu UID do HoYoLab</value>
</data>
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
</data>
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
<value>Estou conectado</value>
</data>

View File

@@ -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>无效的 Uri</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">
@@ -1331,6 +1334,12 @@
<data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve">
<value>为账号命名</value>
</data>
<data name="ViewDialogLaunchGameConfigurationFixDialogHint" xml:space="preserve">
<value>请选择当前游戏路径对应的游戏服务器</value>
</data>
<data name="ViewDialogLaunchGameConfigurationFixDialogTitle" xml:space="preserve">
<value>正在修复配置文件</value>
</data>
<data name="ViewDialogLaunchGamePackageConvertHint" xml:space="preserve">
<value>转换可能需要花费一段时间,请勿关闭胡桃</value>
</data>
@@ -1373,6 +1382,9 @@
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈愿记录</value>
</data>
<data name="ViewGuideStaticResourceDownloadSize" xml:space="preserve">
<value>预计下载大小:{0}</value>
</data>
<data name="ViewGuideStepAgreementIHaveReadText" xml:space="preserve">
<value>我已阅读并同意</value>
</data>
@@ -1388,6 +1400,12 @@
<data name="ViewGuideStepAgreementTermOfService" xml:space="preserve">
<value>用户使用协议与法律声明</value>
</data>
<data name="ViewGuideStepCommonSetting" xml:space="preserve">
<value>基础设置</value>
</data>
<data name="ViewGuideStepCommonSettingHint" xml:space="preserve">
<value>稍后可以在设置中修改</value>
</data>
<data name="ViewGuideStepDocument" xml:space="preserve">
<value>文档</value>
</data>
@@ -1398,7 +1416,7 @@
<value>安装完成后重启胡桃以查看是否正常生效</value>
</data>
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
<value>如果上方的图标中存在乱码,请前往</value>
<value>如果上方的图标中存在乱码或方块字,请前往</value>
</data>
<data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve">
<value>下载并自行安装图标字体</value>
@@ -1415,6 +1433,24 @@
<data name="ViewGuideStepStaticResource" xml:space="preserve">
<value>资源</value>
</data>
<data name="ViewGuideStepStaticResourceSetting" xml:space="preserve">
<value>图像资源设置</value>
</data>
<data name="ViewGuideStepStaticResourceSettingHint" xml:space="preserve">
<value>* 除非你卸载并重新安装胡桃,否则你将无法更改这些设置</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumHeader" xml:space="preserve">
<value>图片资源包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOff" xml:space="preserve">
<value>完整包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingMinimumOn" xml:space="preserve">
<value>精简包体</value>
</data>
<data name="ViewGuideStepStaticResourceSettingQualityHeader" xml:space="preserve">
<value>图片资源质量</value>
</data>
<data name="ViewHutaoDatabaseHeader" xml:space="preserve">
<value>深渊统计</value>
</data>
@@ -1604,6 +1640,12 @@
<data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve">
<value>下载资源文件中,请稍候</value>
</data>
<data name="ViewModelGuideStaticResourceQualityHigh" xml:space="preserve">
<value>高质量</value>
</data>
<data name="ViewModelGuideStaticResourceQualityRaw" xml:space="preserve">
<value>原图</value>
</data>
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
<value>请输入正确的邮箱</value>
</data>
@@ -1622,6 +1664,12 @@
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>切换服务器失败</value>
</data>
<data name="ViewModelLaunchGameFixConfigurationFileButtonText" xml:space="preserve">
<value>修复配置文件</value>
</data>
<data name="ViewModelLaunchGameFixConfigurationFileSuccess" xml:space="preserve">
<value>修复完成</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>识别显示器</value>
</data>
@@ -1634,6 +1682,9 @@
<data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve">
<value>尚未选择任何服务器</value>
</data>
<data name="ViewModelLaunchGameSetGamePathButtonText" xml:space="preserve">
<value>设置游戏目录</value>
</data>
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>切换账号失败</value>
</data>
@@ -2342,6 +2393,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>
@@ -2828,6 +2882,15 @@
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
<value>上传数据</value>
</data>
<data name="ViewTitileUpdatePackageDownloadContent" xml:space="preserve">
<value>是否立即下载</value>
</data>
<data name="ViewTitileUpdatePackageDownloadFailedMessage" xml:space="preserve">
<value>下载更新失败</value>
</data>
<data name="ViewTitileUpdatePackageDownloadTitle" xml:space="preserve">
<value>胡桃 {0} 版本已发布</value>
</data>
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
<value>是否立即安装?</value>
</data>
@@ -2837,6 +2900,9 @@
<data name="ViewTitleAutoClicking" xml:space="preserve">
<value>自动连点</value>
</data>
<data name="ViewTitleUpdatePackageInstallingContent" xml:space="preserve">
<value>正在安装更新</value>
</data>
<data name="ViewToolHeader" xml:space="preserve">
<value>工具</value>
</data>

View File

@@ -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>Ошибка ссылки</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>В одном архиве достижений обнаружено несколько одинаковых идентификаторов достижений</value>
</data>
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
@@ -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>

View File

@@ -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>
@@ -121,7 +121,7 @@
<value>胡桃 Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>胡桃Dev {0} [管理员]</value>
<value>胡桃Dev {0} [系統管理员]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>胡桃 {0} [系統管理員]</value>
@@ -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>無效的 Uri</value>
</data>
@@ -166,7 +169,7 @@
<value>用户數據已損壞:{0}</value>
</data>
<data name="CoreIOPickerExtensionPickerExceptionInfoBarMessage" xml:space="preserve">
<value>請勿在管理員模式下使用此功能 {0}</value>
<value>請勿在系統管理員模式下使用此功能 {0}</value>
</data>
<data name="CoreIOPickerExtensionPickerExceptionInfoBarTitle" xml:space="preserve">
<value>無法打開文件選擇器</value>
@@ -274,7 +277,7 @@
<value>尚未重新整理</value>
</data>
<data name="ModelEntityDailyNoteRefreshTimeFormat" xml:space="preserve">
<value>重新整理 {0:MM.dd HH:mm:ss}</value>
<value>重新整理 {0:MM.dd HH:mm:ss}</value>
</data>
<data name="ModelEntitySpiralAbyssScheduleFormat" xml:space="preserve">
<value>第 {0} 期</value>
@@ -540,28 +543,28 @@
<value>必须登入 米遊社/HoYoLAB 並選定一個用戶與角色</value>
</data>
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
<value>刪除了 Uid{0} 的 {1} 筆祈願記錄</value>
<value>刪除了 UID{0} 的 {1} 筆祈願記錄</value>
</data>
<data name="ServerGachaLogServiceInsufficientRecordSlot" xml:space="preserve">
<value>胡桃雲存的祈願記錄存檔數已達當前帳號上限</value>
<value>胡桃雲存的祈願記錄存檔數已達當前帳號上限</value>
</data>
<data name="ServerGachaLogServiceInsufficientTime" xml:space="preserve">
<value>未開通祈願紀錄上傳服務或已到期</value>
</data>
<data name="ServerGachaLogServiceInvalidGachaLogData" xml:space="preserve">
<value>祈願數據存在無效的物品,無法存至胡桃雲</value>
<value>祈願數據存在無效的物品,無法存至胡桃雲</value>
</data>
<data name="ServerGachaLogServiceServerDatabaseError" xml:space="preserve">
<value>數據異常,無法存至雲端,請勿跨帳號上傳或嘗試删除雲端數據後重試</value>
<value>數據異常,無法存至雲端,請勿跨帳號上傳或嘗試删除雲端數據後重試</value>
</data>
<data name="ServerGachaLogServiceUploadEntrySucceed" xml:space="preserve">
<value>上傳了 Uid{0} 的 {1} 筆祈願記錄,儲存了 {2} 筆</value>
<value>上傳了 UID{0} 的 {1} 筆祈願記錄,儲存了 {2} 筆</value>
</data>
<data name="ServerPassportLoginRequired" xml:space="preserve">
<value>請先登或注冊胡桃帳號</value>
<value>請先登或注冊胡桃帳號</value>
</data>
<data name="ServerPassportLoginSucceed" xml:space="preserve">
<value>登成功</value>
<value>登成功</value>
</data>
<data name="ServerPassportRegisterSucceed" xml:space="preserve">
<value>註冊成功</value>
@@ -594,7 +597,7 @@
<value>驗證失敗</value>
</data>
<data name="ServerPassportVerifyRequestNotCurrentUser" xml:space="preserve">
<value>驗證請求失敗,不是當前登的帳號</value>
<value>驗證請求失敗,不是當前登的帳號</value>
</data>
<data name="ServerPassportVerifyRequestSuccess" xml:space="preserve">
<value>驗證碼已發送至郵箱</value>
@@ -606,7 +609,7 @@
<value>驗證請求過快,請 1 分鐘後再試</value>
</data>
<data name="ServerRecordBannedUid" xml:space="preserve">
<value>上傳深淵記錄失敗,當前 Uid 已被胡桃數據庫封禁</value>
<value>上傳深淵記錄失敗,當前 UID 已被胡桃數據庫封禁</value>
</data>
<data name="ServerRecordComputingStatistics" xml:space="preserve">
<value>上傳深淵記錄失敗,正在計算統計數據</value>
@@ -621,19 +624,19 @@
<value>上傳深淵記錄失敗,存在無效的數據</value>
</data>
<data name="ServerRecordInvalidUid" xml:space="preserve">
<value>無效的 Uid</value>
<value>無效的 UID</value>
</data>
<data name="ServerRecordNotCurrentSchedule" xml:space="preserve">
<value>上傳深淵記錄失敗,不是本期數據</value>
</data>
<data name="ServerRecordPreviousRequestNotCompleted" xml:space="preserve">
<value>上傳深淵記錄失敗,當前 Uid 的紀錄仍在處理中,請勿重複操作</value>
<value>上傳深淵記錄失敗,當前 UID 的紀錄仍在處理中,請勿重複操作</value>
</data>
<data name="ServerRecordUploadSuccessAndGachaLogServiceTimeExtended" xml:space="preserve">
<value>上傳深淵記錄成功,獲贈祈願記錄上傳服務時長</value>
</data>
<data name="ServerRecordUploadSuccessButNoPassport" xml:space="preserve">
<value>上傳深淵記錄成功,但未登胡桃通行證,無法獲贈祈願記錄上傳服務時長</value>
<value>上傳深淵記錄成功,但未登胡桃通行證,無法獲贈祈願記錄上傳服務時長</value>
</data>
<data name="ServerRecordUploadSuccessButNoSuchUser" xml:space="preserve">
<value>上傳深淵記錄成功,但無法找到使用者,無法獲贈祈願記錄上傳服務時長</value>
@@ -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">
@@ -762,19 +765,19 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ServiceAvatarInfoSummaryCalculatorNotRefreshed" xml:space="preserve">
<value>養成計算:尚未刷新</value>
<value>養成計算:尚未重新整理</value>
</data>
<data name="ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat" xml:space="preserve">
<value>養成計算:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>原神戰績:尚未更新</value>
<value>原神戰績:尚未重新整理</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>原神戰績:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>角色櫥窗:尚未刷新</value>
<value>角色櫥窗:尚未重新整理</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色櫥窗:{0:MM-dd HH:mm}</value>
@@ -795,7 +798,7 @@
<value>無背景圖片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>存養成計劃狀態失敗</value>
<value>存養成計劃狀態失敗</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted2" xml:space="preserve">
<value>存在多個選中的養成計劃</value>
@@ -849,7 +852,7 @@
<value>準備完成</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>參質變儀已準備完成</value>
<value>參質變儀已準備完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>權限不足,將無法為您設定 Discord Activity 狀態</value>
@@ -879,7 +882,7 @@
<value>神鑄賦形</value>
</data>
<data name="ServiceGachaLogHutaoCloudEndIdFetchFailed" xml:space="preserve">
<value>獲取祈願紀錄失敗</value>
<value>獲取雲端祈願紀錄失敗</value>
</data>
<data name="ServiceGachaLogHutaoCloudServiceNotAllowed" xml:space="preserve">
<value>祈願記錄上傳服務不可用</value>
@@ -897,19 +900,19 @@
<value>找不到原神內置瀏覽器緩存路徑:\n{0}</value>
</data>
<data name="ServiceGachaLogUrlProviderCacheUrlNotFound" xml:space="preserve">
<value>找不到可用的 Url</value>
<value>找不到可用的 URL</value>
</data>
<data name="ServiceGachaLogUrlProviderManualInputInvalid" xml:space="preserve">
<value>提供的 Url 無效</value>
<value>提供的 URL 無效</value>
</data>
<data name="ServiceGachaLogUrlProviderStokenUnsupported" xml:space="preserve">
<value>HoYoLAB 賬號不支持使用 SToken 刷新祈願記錄</value>
<value>HoYoLAB 賬號不支持使用 SToken 重新整理祈願記錄</value>
</data>
<data name="ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale" xml:space="preserve">
<value>Url 中的語言:{0} 與胡桃的語言:{1} 不對應,請切換到對應語言重試</value>
<value>URL 中的語言:{0} 與胡桃的語言:{1} 不對應,請切換到對應語言重試</value>
</data>
<data name="ServiceGachaStatisticsFactoryItemIdInvalid" xml:space="preserve">
<value>不支持的 Item Id: {0}</value>
<value>不支持的 Item Id{0}</value>
</data>
<data name="ServiceGachaUIGFImportLanguageNotMatch" xml:space="preserve">
<value>UIGF 文件的語言:{0} 與胡桃的語言:{1} 不對應,請切換到對應語言重試</value>
@@ -921,7 +924,7 @@
<value>文件系統權限不足,無法轉換伺服器</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>下载游戏资源索引</value>
<value>下載遊戲資源索引</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>遊戲檔案操作失敗:{0}</value>
@@ -936,7 +939,7 @@
<value>請選擇遊戲路徑</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>下载游戏资源索引失: {0}</value>
<value>下載遊戲資源索引失: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>遊戲進程已退出</value>
@@ -996,13 +999,13 @@
<value>無法找到遊戲本體路徑,請前往設定修改</value>
</data>
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
<value>未開長路徑功能,無法設定冊表鍵值</value>
<value>未開長路徑功能,無法設定冊表鍵值</value>
</data>
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
<value>無法讀取遊戲設定檔 {0},可能是檔案不存在</value>
</data>
<data name="ServiceGameSetMultiChannelUnauthorizedAccess" xml:space="preserve">
<value>無法讀取或存配置文件,請用管理員模式重試</value>
<value>無法讀取或存配置文件,請用系統管理員模式重試</value>
</data>
<data name="ServiceGameUnlockerFindModuleNoModuleFound" xml:space="preserve">
<value>在尋找必要的模組時遇到問題:無法讀取任何模組,可能是保護驅動已經載入完成,請重試</value>
@@ -1047,13 +1050,13 @@
<value>獲取簽到次數失敗</value>
</data>
<data name="ServiceSignInRewardListRequestFailed" xml:space="preserve">
<value>獲取獎勵列表失敗</value>
<value>獲取獎勵清單失敗</value>
</data>
<data name="ServiceSignInRiskVerificationFailed" xml:space="preserve">
<value>驗證失敗請前往HoYoLab原神簽到頁面自行領取獎勵</value>
<value>驗證失敗請前往HoYoLAB原神簽到頁面自行領取獎勵</value>
</data>
<data name="ServiceSignInSuccessRewardFormat" xml:space="preserve">
<value>到成功,{0}×{1}</value>
<value>到成功,{0}×{1}</value>
</data>
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
<value>不支援的 UIGF 版本</value>
@@ -1065,13 +1068,13 @@
<value>已选中多条用户记录</value>
</data>
<data name="ServiceUserCurrentUpdateAndSaveFailed" xml:space="preserve">
<value>用戶 {0} 狀態存失敗</value>
<value>用戶 {0} 狀態存失敗</value>
</data>
<data name="ServiceUserProcessCookieNoMid" xml:space="preserve">
<value>Mid 必須包含在輸入的 Cookie 中</value>
<value>輸入的 Cookie 中必須包含 Mid</value>
</data>
<data name="ServiceUserProcessCookieNoSToken" xml:space="preserve">
<value>輸入的 Cookie 中必須包含 Stoken 字段</value>
<value>輸入的 Cookie 中必須包含 SToken</value>
</data>
<data name="ServiceUserProcessCookieRequestUserInfoFailed" xml:space="preserve">
<value>輸入的 Cookie 無法獲取用戶信息</value>
@@ -1104,7 +1107,7 @@
<value>突破後</value>
</data>
<data name="ViewControlElevationText" xml:space="preserve">
<value>需要管理權限</value>
<value>需要系統管理權限</value>
</data>
<data name="ViewControlLoadingText" xml:space="preserve">
<value>加載中,請等候</value>
@@ -1233,10 +1236,10 @@
<value>參數質變儀提醒</value>
</data>
<data name="ViewDialogDailyNoteWebhookUrlInputPlaceholder" xml:space="preserve">
<value>請輸入 Url</value>
<value>請輸入 URL</value>
</data>
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>即時便箋 Webhook Url</value>
<value>即時便箋 Webhook URL</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制後需使用其他工具恢復限制</value>
@@ -1257,13 +1260,13 @@
<value>獲取祈願物品中</value>
</data>
<data name="ViewDialogGachaLogUrlInputPlaceholder" xml:space="preserve">
<value>請輸入 Url</value>
<value>請輸入 URL</value>
</data>
<data name="ViewDialogGachaLogUrlTitle" xml:space="preserve">
<value>手動輸入祈願記錄 Url</value>
<value>手動輸入祈願記錄 URL</value>
</data>
<data name="ViewDialogGeetestCustomUrlCompositInputHint" xml:space="preserve">
<value>請輸入請求接口的 Url 複合模板</value>
<value>請輸入請求接口的 URL 複合模板</value>
</data>
<data name="ViewDialogGeetestCustomUrlReturnDataDescription1" xml:space="preserve">
<value>接口需要返回形如上方所示的 Json 數據,多餘的數據會被忽略</value>
@@ -1293,7 +1296,7 @@
<value>登入胡桃通行證</value>
</data>
<data name="ViewDialogHutaoPassportRegisterTitle" xml:space="preserve">
<value>建立胡桃通行證用戶</value>
<value>註冊胡桃通行證</value>
</data>
<data name="ViewDialogHutaoPassportResetPasswordTitle" xml:space="preserve">
<value>重設胡桃通行證用戶密碼</value>
@@ -1347,7 +1350,7 @@
<value>你正在啟用一個危險功能</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>該操作是不可逆,所有用戶登狀態會遺失</value>
<value>該操作是不可逆,所有用戶登狀態會遺失</value>
</data>
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久刪除用戶數據</value>
@@ -1395,7 +1398,7 @@
<value>環境</value>
</data>
<data name="ViewGuideStepEnvironmentAfterInstallDescription" xml:space="preserve">
<value>安裝完成後重胡桃以查看是否正常生效</value>
<value>安裝完成後重新啟動胡桃以查看是否正常生效</value>
</data>
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
<value>如果上方的圖標中存在亂碼,請前往</value>
@@ -1407,7 +1410,7 @@
<value>若未檢測到 WebView2 Runtime信息可以前往</value>
</data>
<data name="ViewGuideStepEnvironmentWebView2Description2" xml:space="preserve">
<value>下載並自行安裝Runtime</value>
<value>下載並自行安裝運行時</value>
</data>
<data name="ViewGuideStepLanguage" xml:space="preserve">
<value>語言</value>
@@ -1425,7 +1428,7 @@
<value>啟動遊戲</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理員模式下無法拖動排序</value>
<value>系統管理員模式下無法拖動排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存檔 [{0}] 添加成功</value>
@@ -1506,7 +1509,7 @@
<value>不能新增名稱無效的計劃</value>
</data>
<data name="ViewModelDailyNoteConfigWebhookUrlComplete" xml:space="preserve">
<value>即時便箋 Webhook Url 配置成功</value>
<value>即時便箋 Webhook URL 配置成功</value>
</data>
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
<value>HoYoLAB 賬號不支持驗證實时便箋</value>
@@ -1572,7 +1575,7 @@
<value>獲取祈願紀錄失敗</value>
</data>
<data name="ViewModelGachaLogRefreshOperationCancel" xml:space="preserve">
<value>祈願紀錄刷新操作被異常取消</value>
<value>祈願紀錄重新整理操作被異常取消</value>
</data>
<data name="ViewModelGachaLogRemoveArchiveDescription" xml:space="preserve">
<value>該操作是不可逆的,該存檔和其內的所有祈願紀錄會丟失</value>
@@ -1629,7 +1632,7 @@
<value>無法讀取遊戲設定檔案: {0},可能是檔案不存在或權限不足</value>
</data>
<data name="ViewModelLaunchGamePathInvalid" xml:space="preserve">
<value>戲程式路徑不正確,前往設定更改遊戲路徑</value>
<value>戲程式路徑不正確,前往設定更改遊戲路徑</value>
</data>
<data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve">
<value>還未選擇任何伺服器</value>
@@ -1644,7 +1647,7 @@
<value>操作完成</value>
</data>
<data name="ViewModelSettingClearWebCacheFail" xml:space="preserve">
<value>清除失敗,文件目錄權限不足,請使用管理員模式重試</value>
<value>清除失敗,文件目錄權限不足,請使用系統管理員模式重試</value>
</data>
<data name="ViewModelSettingClearWebCachePathInvalid" xml:space="preserve">
<value>清除失敗,找不到目錄:{0}</value>
@@ -1668,10 +1671,10 @@
<value>已使用磁碟空間:{0}</value>
</data>
<data name="ViewModelSettingGeetestCustomUrlSucceed" xml:space="preserve">
<value>無感驗證復合 Url 配置成功</value>
<value>無感驗證復合 URL 配置成功</value>
</data>
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>設置數據目錄成功,重以應用更改</value>
<value>設置數據目錄成功,重新啟動以應用更改</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>儲存遊戲路徑失敗</value>
@@ -1737,7 +1740,7 @@
<value>刪除當前存檔</value>
</data>
<data name="ViewPageAchievementSearchPlaceholder" xml:space="preserve">
<value>搜成就名稱,描述,版本或編號</value>
<value>搜成就名稱,描述,版本或編號</value>
</data>
<data name="ViewPageAchievementSortIncompletedItemsFirst" xml:space="preserve">
<value>優先未完成</value>
@@ -1797,7 +1800,7 @@
<value>同步角色天賦外的大部分信息</value>
</data>
<data name="ViewPageAvatarPropertyRefreshTimeToggle" xml:space="preserve">
<value>刷新時間</value>
<value>重新整理時間</value>
</data>
<data name="ViewPageAvatarPropertyScore" xml:space="preserve">
<value>評分</value>
@@ -1809,13 +1812,13 @@
<value>養成計算</value>
</data>
<data name="ViewPageCultivationAddProject" xml:space="preserve">
<value>新計劃</value>
<value>新計劃</value>
</data>
<data name="ViewPageCultivationAddProjectAction" xml:space="preserve">
<value>新增</value>
</data>
<data name="ViewPageCultivationAddProjectContinue" xml:space="preserve">
<value>新養成計劃以繼續</value>
<value>新養成計劃以繼續</value>
</data>
<data name="ViewPageCultivationAddProjectDescription" xml:space="preserve">
<value>稍後可以前往其他頁面添加養成計劃條目</value>
@@ -1863,7 +1866,7 @@
<value>歷練點獲取詳情</value>
</data>
<data name="ViewPageDailyNoteConfigWebhookDescription" xml:space="preserve">
<value>在即時便箋重之後推送到指定的 Webhook</value>
<value>在即時便箋重新整理之後推送到指定的 Webhook</value>
</data>
<data name="ViewPageDailyNoteConfigWebhookHeader" xml:space="preserve">
<value>配置 Webhook</value>
@@ -1896,13 +1899,13 @@
<value>本周已消耗減半次數</value>
</data>
<data name="ViewPageDailyNoteSettingAutoRefresh" xml:space="preserve">
<value>自動刷新</value>
<value>自動重新整理</value>
</data>
<data name="ViewPageDailyNoteSettingAutoRefreshDescription" xml:space="preserve">
<value>間隔選定的時間後刷新添加的實時便箋</value>
<value>間隔選定的時間後重新整理添加的實時便箋</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshElevatedHint" xml:space="preserve">
<value>這些選項僅允許在非管理員模式下更改</value>
<value>這些選項僅允許在非系統管理員模式下更改</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshHeader" xml:space="preserve">
<value>重新整理</value>
@@ -1959,7 +1962,7 @@
<value>胡桃服務</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>全量式重</value>
<value>全量式重新整理</value>
</data>
<data name="ViewPageGachaLogExportAction" xml:space="preserve">
<value>匯出</value>
@@ -2019,22 +2022,22 @@
<value>獲取</value>
</data>
<data name="ViewPageGachaLogRefreshByManualInput" xml:space="preserve">
<value>手動輸入 Url</value>
<value>手動輸入 URL</value>
</data>
<data name="ViewPageGachaLogRefreshByManualInputDescription" xml:space="preserve">
<value>使用由你提供的 Url 刷新祈願記錄</value>
<value>使用由你提供的 URL 重新整理祈願記錄</value>
</data>
<data name="ViewPageGachaLogRefreshBySToken" xml:space="preserve">
<value>SToken 重</value>
<value>SToken 重新整理</value>
</data>
<data name="ViewPageGachaLogRefreshBySTokenDescription" xml:space="preserve">
<value>使用當前用戶的 Cookie 信息刷新祈願紀錄</value>
<value>使用當前用戶的 Cookie 信息重新整理祈願紀錄</value>
</data>
<data name="ViewPageGachaLogRefreshByWebCache" xml:space="preserve">
<value>網頁緩存刷新</value>
<value>網頁緩存重新整理</value>
</data>
<data name="ViewPageGachaLogRefreshByWebCacheDescription" xml:space="preserve">
<value>使用遊戲內瀏覽器的網頁快取刷新祈願紀錄</value>
<value>使用遊戲內瀏覽器的網頁快取重新整理祈願紀錄</value>
</data>
<data name="ViewPageGachaLogRemoveArchiveAction" xml:space="preserve">
<value>刪除當前存檔</value>
@@ -2118,7 +2121,7 @@
<value>上傳記錄總數</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewRefreshTime" xml:space="preserve">
<value>數據刷新時間</value>
<value>數據重新整理時間</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewSpiralAbyss" xml:space="preserve">
<value>深淵數據統計</value>
@@ -2151,7 +2154,7 @@
<value>至少需要八個字元</value>
</data>
<data name="ViewPageHutaoPassportRegisterHeader" xml:space="preserve">
<value>建立帳號</value>
<value>註冊</value>
</data>
<data name="ViewPageHutaoPassportResetPasswordHeader" xml:space="preserve">
<value>重設密碼</value>
@@ -2193,7 +2196,7 @@
<value>啟用內置觸摸布局,不會響應鍵鼠輸入</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveDescription" xml:space="preserve">
<value>與戲内瀏覽器不兼容,切屏等操作也能使戲閃退</value>
<value>與戲内瀏覽器不兼容,切屏等操作也能使戲閃退</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveHeader" xml:space="preserve">
<value>獨占全屏</value>
@@ -2235,7 +2238,7 @@
<value>一般</value>
</data>
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
<value>所有選項盡會在啓動游戲成功後存</value>
<value>所有選項盡會在啟動遊戲成功後存</value>
</data>
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
<value>在我遊戲時設定 Discord Activity 狀態</value>
@@ -2342,11 +2345,14 @@
<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>
<value>我已登</value>
</data>
<data name="ViewPageLoginMihoyoUserTitle" xml:space="preserve">
<value>在下方登錄 MiHoYo 通行證賬號</value>
<value>在下方登入 miHoYo 通行證賬號</value>
</data>
<data name="ViewPageOpenScreenshotFolderAction" xml:space="preserve">
<value>開啟截圖資料夾</value>
@@ -2376,7 +2382,7 @@
<value>圖片版權訊息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改視窗的背景圖片來源,重胡桃以盡快生效</value>
<value>更改視窗的背景圖片來源,重新啟動胡桃以盡快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景圖片</value>
@@ -2424,7 +2430,7 @@
<value>刪除</value>
</data>
<data name="ViewPageSettingDeleteCacheDescription" xml:space="preserve">
<value>若祈願紀錄緩存刷新頻繁提示驗證密鑰過期,可以嘗試此操作</value>
<value>若祈願紀錄緩存重新整理頻繁提示驗證密鑰過期,可以嘗試此操作</value>
</data>
<data name="ViewPageSettingDeleteCacheHeader" xml:space="preserve">
<value>刪除游戲内網頁緩存</value>
@@ -2451,7 +2457,7 @@
<value>系統管理員模式</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>以系統管理員身分重啟動</value>
<value>以系統管理員身分重啟動</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>在祈願紀錄頁面顯示或隱藏無記錄的歷史祈願活動</value>
@@ -2466,7 +2472,7 @@
<value>顯示</value>
</data>
<data name="ViewPageSettingFeaturesDangerousHint" xml:space="preserve">
<value>您解鎖了含有違反原神服務條款風險的「啟動遊戲-高級功能」,將自行承擔任何不良後果。</value>
<value>您解鎖了含有違反原神服務條款風險的「啟動遊戲-進階功能」,將自行承擔任何不良後果。</value>
</data>
<data name="ViewPageSettingFeedbackNavigate" xml:space="preserve">
<value>前往反饋</value>
@@ -2532,7 +2538,7 @@
<value>胡桃雲服務到期時間</value>
</data>
<data name="ViewPageSettingHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行證帳號</value>
<value>胡桃通行證帳號</value>
</data>
<data name="ViewPageSettingHutaoPassportLicensedDeveloperDescription" xml:space="preserve">
<value>您可以無限制使用任何基於胡桃雲服務的功能</value>
@@ -2559,7 +2565,7 @@
<value>使用兌換碼</value>
</data>
<data name="ViewPageSettingHutaoPassportRegisterAction" xml:space="preserve">
<value>建立帳號</value>
<value>註冊</value>
</data>
<data name="ViewPageSettingHutaoPassportResetPasswordAction" xml:space="preserve">
<value>重設密碼</value>
@@ -2568,10 +2574,10 @@
<value>刪除帳號</value>
</data>
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledDescription" xml:space="preserve">
<value>在完整閱讀原神和胡桃工具箱使用者協定後,我選擇啟用「啟動遊戲 - 高級功能」</value>
<value>在完整閱讀原神和胡桃工具箱使用者協定後,我選擇啟用「啟動遊戲 - 進階功能」</value>
</data>
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
<value>啟動高級功能</value>
<value>進階功能</value>
</data>
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
<value>更改自動連續點按功能的快速鍵</value>
@@ -2601,10 +2607,10 @@
<value>重設圖片資源</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在啟動遊戲頁面的程序部分加入解鎖幀率限制選項</value>
<value>在啟動遊戲頁面的程序部分加入解鎖 FPS 限制選項</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>啟動遊戲-解鎖幀率限制</value>
<value>啟動遊戲-解鎖 FPS 限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>更改目錄后需要手動移動目錄内的數據,否則會重新創建用戶數據</value>
@@ -2742,7 +2748,7 @@
<value>登入失敗,請重新登入</value>
</data>
<data name="ViewServiceHutaoUserLoginOrRegisterHint" xml:space="preserve">
<value>立即登入或建立帳號</value>
<value>立即登入或註冊</value>
</data>
<data name="ViewSettingAllocConsoleDescription" xml:space="preserve">
<value>控制胡桃啟動時是否開啟主控台,重新啟動後生效</value>
@@ -2811,7 +2817,7 @@
<value>重新整理數據</value>
</data>
<data name="ViewSpiralAbyssRefreshDescription" xml:space="preserve">
<value>同步 HoYo-LAB 的深淵挑戰記錄</value>
<value>同步 HoYoLAB 的深淵挑戰記錄</value>
</data>
<data name="ViewSpiralAbyssReveal" xml:space="preserve">
<value>出戰次數</value>
@@ -2880,10 +2886,10 @@
<value>尚未登入</value>
</data>
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
<value>更新 CookieToken 成功</value>
<value>重新整理 CookieToken 成功</value>
</data>
<data name="ViewUserRefreshCookieTokenWarning" xml:space="preserve">
<value>更新 CookieToken 失敗</value>
<value>重新整理 CookieToken 失敗</value>
</data>
<data name="ViewUserRemoveAction" xml:space="preserve">
<value>移除用戶</value>
@@ -3030,7 +3036,7 @@
<value>尚未獲得</value>
</data>
<data name="WebDailyNoteTransformerNotObtainedDetail" xml:space="preserve">
<value>尚未獲得參質變儀</value>
<value>尚未獲得參質變儀</value>
</data>
<data name="WebDailyNoteTransformerNotReached" xml:space="preserve">
<value>冷卻中</value>
@@ -3123,7 +3129,7 @@
<value>狀態:{0} | 信息:{1}</value>
</data>
<data name="WebResponseRefreshCookieHintFormat" xml:space="preserve">
<value>請更新 Cookie原始消息{0}</value>
<value>請重新整理 Cookie原始消息{0}</value>
</data>
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
<value>[{0}] 中的 [{1}] 網路請求異常,請稍後再試</value>

View File

@@ -0,0 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Abstraction;
namespace Snap.Hutao.Service.Abstraction;
internal static class AppDbServiceAppDbEntityExtension
{
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class, IAppDbEntity
{
return service.Execute(dbset => dbset.ExecuteDeleteWhere(e => e.InnerId == entity.InnerId));
}
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class, IAppDbEntity
{
return service.ExecuteAsync((dbset, token) => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId, token), token);
}
public static List<TEntity> ListByArchiveId<TEntity>(this IAppDbService<TEntity> service, Guid archiveId)
where TEntity : class, IAppDbEntityHasArchive
{
return service.Query(query => query.Where(e => e.ArchiveId == archiveId).ToList());
}
public static ValueTask<List<TEntity>> ListByArchiveIdAsync<TEntity>(this IAppDbService<TEntity> service, Guid archiveId, CancellationToken token = default)
where TEntity : class, IAppDbEntityHasArchive
{
return service.QueryAsync((query, token) => query.Where(e => e.ArchiveId == archiveId).ToListAsync(token), token);
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
namespace Snap.Hutao.Service.Abstraction;
internal static class AppDbServiceCollectionExtension
{
public static List<TEntity> List<TEntity>(this IAppDbService<TEntity> service)
where TEntity : class
{
return service.Query(query => query.ToList());
}
public static List<TEntity> List<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Query(query => query.Where(predicate).ToList());
}
public static ValueTask<List<TEntity>> ListAsync<TEntity>(this IAppDbService<TEntity> service, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query, token) => query.ToListAsync(token), token);
}
public static ValueTask<List<TEntity>> ListAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query, token) => query.Where(predicate).ToListAsync(token), token);
}
public static ObservableCollection<TEntity> ObservableCollection<TEntity>(this IAppDbService<TEntity> service)
where TEntity : class
{
return service.Query(query => query.ToObservableCollection());
}
public static ObservableCollection<TEntity> ObservableCollection<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Query(query => query.Where(predicate).ToObservableCollection());
}
}

View File

@@ -0,0 +1,152 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Database;
using System.Linq.Expressions;
namespace Snap.Hutao.Service.Abstraction;
internal static class AppDbServiceExtension
{
public static TResult Execute<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, TResult> func)
where TEntity : class
{
using (IServiceScope scope = service.ServiceProvider.CreateScope())
{
AppDbContext appDbContext = scope.GetAppDbContext();
return func(appDbContext.Set<TEntity>());
}
}
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, ValueTask<TResult>> asyncFunc)
where TEntity : class
{
using (IServiceScope scope = service.ServiceProvider.CreateScope())
{
AppDbContext appDbContext = scope.GetAppDbContext();
return await asyncFunc(appDbContext.Set<TEntity>()).ConfigureAwait(false);
}
}
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, CancellationToken, ValueTask<TResult>> asyncFunc, CancellationToken token)
where TEntity : class
{
using (IServiceScope scope = service.ServiceProvider.CreateScope())
{
AppDbContext appDbContext = scope.GetAppDbContext();
return await asyncFunc(appDbContext.Set<TEntity>(), token).ConfigureAwait(false);
}
}
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, Task<TResult>> asyncFunc)
where TEntity : class
{
using (IServiceScope scope = service.ServiceProvider.CreateScope())
{
AppDbContext appDbContext = scope.GetAppDbContext();
return await asyncFunc(appDbContext.Set<TEntity>()).ConfigureAwait(false);
}
}
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, CancellationToken, Task<TResult>> asyncFunc, CancellationToken token)
where TEntity : class
{
using (IServiceScope scope = service.ServiceProvider.CreateScope())
{
AppDbContext appDbContext = scope.GetAppDbContext();
return await asyncFunc(appDbContext.Set<TEntity>(), token).ConfigureAwait(false);
}
}
public static int Add<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class
{
return service.Execute(dbset => dbset.AddAndSave(entity));
}
public static ValueTask<int> AddAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.AddAndSaveAsync(entity, token), token);
}
public static int AddRange<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities)
where TEntity : class
{
return service.Execute(dbset => dbset.AddRangeAndSave(entities));
}
public static ValueTask<int> AddRangeAsync<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.AddRangeAndSaveAsync(entities, token), token);
}
public static TResult Query<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, TResult> func)
where TEntity : class
{
return service.Execute(dbset => func(dbset.AsNoTracking()));
}
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, ValueTask<TResult>> func)
where TEntity : class
{
return service.ExecuteAsync(dbset => func(dbset.AsNoTracking()));
}
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, CancellationToken, ValueTask<TResult>> func, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => func(dbset.AsNoTracking(), token), token);
}
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, Task<TResult>> func)
where TEntity : class
{
return service.ExecuteAsync(dbset => func(dbset.AsNoTracking()));
}
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, CancellationToken, Task<TResult>> func, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => func(dbset.AsNoTracking(), token), token);
}
public static TEntity Single<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Query(query => query.Single(predicate));
}
public static ValueTask<TEntity> SingleAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query, token) => query.SingleAsync(predicate, token), token);
}
public static int Update<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class
{
return service.Execute(dbset => dbset.UpdateAndSave(entity));
}
public static ValueTask<int> UpdateAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.UpdateAndSaveAsync(entity, token), token);
}
public static int Delete<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class
{
return service.Execute(dbset => dbset.RemoveAndSave(entity));
}
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.RemoveAndSaveAsync(entity, token), token);
}
}

View File

@@ -14,20 +14,10 @@ namespace Snap.Hutao.Service.Abstraction;
/// 数据库存储选项的设置
/// </summary>
[ConstructorGenerated]
internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbStoreOptions>
internal abstract partial class DbStoreOptions : ObservableObject
{
private readonly IServiceProvider serviceProvider;
/// <inheritdoc/>
public DbStoreOptions Value { get => this; }
/// <summary>
/// 从数据库中获取字符串数据
/// </summary>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>值</returns>
protected string GetOption(ref string? storage, string key, string defaultValue = "")
{
return GetOption(ref storage, key, () => defaultValue);
@@ -49,13 +39,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
return storage;
}
/// <summary>
/// 从数据库中获取bool数据
/// </summary>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>值</returns>
protected bool GetOption(ref bool? storage, string key, bool defaultValue = false)
{
return GetOption(ref storage, key, () => defaultValue);
@@ -78,13 +61,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
return storage.Value;
}
/// <summary>
/// 从数据库中获取int数据
/// </summary>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>值</returns>
protected int GetOption(ref int? storage, string key, int defaultValue = 0)
{
return GetOption(ref storage, key, () => defaultValue);
@@ -107,15 +83,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
return storage.Value;
}
/// <summary>
/// 从数据库中获取任何类型的数据
/// </summary>
/// <typeparam name="T">数据的类型</typeparam>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="deserializer">反序列化器</param>
/// <param name="defaultValue">默认值</param>
/// <returns>值</returns>
[return: NotNull]
protected T GetOption<T>(ref T? storage, string key, Func<string, T> deserializer, [DisallowNull] T defaultValue)
{
@@ -160,13 +127,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
return storage;
}
/// <summary>
/// 将值存入数据库
/// </summary>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <param name="propertyName">属性名称</param>
protected void SetOption(ref string? storage, string key, string? value, [CallerMemberName] string? propertyName = null)
{
if (!SetProperty(ref storage, value, propertyName))
@@ -182,14 +142,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
}
}
/// <summary>
/// 将值存入数据库
/// </summary>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <param name="propertyName">属性名称</param>
/// <returns>是否设置了值</returns>
protected bool SetOption(ref bool? storage, string key, bool value, [CallerMemberName] string? propertyName = null)
{
bool set = SetProperty(ref storage, value, propertyName);
@@ -208,13 +160,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
return set;
}
/// <summary>
/// 将值存入数据库
/// </summary>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <param name="propertyName">属性名称</param>
protected void SetOption(ref int? storage, string key, int value, [CallerMemberName] string? propertyName = null)
{
if (!SetProperty(ref storage, value, propertyName))
@@ -230,15 +175,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
}
}
/// <summary>
/// 将值存入数据库
/// </summary>
/// <typeparam name="T">数据的类型</typeparam>
/// <param name="storage">存储字段</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <param name="serializer">序列化器</param>
/// <param name="propertyName">属性名称</param>
protected void SetOption<T>(ref T? storage, string key, T value, Func<T, string> serializer, [CallerMemberName] string? propertyName = null)
{
if (!SetProperty(ref storage, value, propertyName))

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction;
internal interface IAppDbService<TEntity> : IAppInfrastructureService
where TEntity : class
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction;
internal interface IAppInfrastructureService
{
IServiceProvider ServiceProvider { get; }
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction;
internal interface IAppService;

View File

@@ -0,0 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model.Entity.Database;
namespace Snap.Hutao.Service.Abstraction;
internal static class ServiceScopeExtension
{
public static TService GetRequiredService<TService>(this IServiceScope scope)
where TService : class
{
return scope.ServiceProvider.GetRequiredService<TService>();
}
public static TDbContext GetDbContext<TDbContext>(this IServiceScope scope)
where TDbContext : DbContext
{
return scope.GetRequiredService<TDbContext>();
}
public static AppDbContext GetAppDbContext(this IServiceScope scope)
{
return scope.GetDbContext<AppDbContext>();
}
}

View File

@@ -2,9 +2,11 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Collection;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Service.Abstraction;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
namespace Snap.Hutao.Service.Achievement;
@@ -21,197 +23,155 @@ internal sealed partial class AchievementDbBulkOperation
private readonly IServiceProvider serviceProvider;
private readonly ILogger<AchievementDbBulkOperation> logger;
/// <summary>
/// 合并
/// </summary>
/// <param name="archiveId">成就id</param>
/// <param name="items">待合并的项</param>
/// <param name="aggressive">是否贪婪</param>
/// <returns>导入结果</returns>
public ImportResult Merge(Guid archiveId, IEnumerable<UIAFItem> items, bool aggressive)
{
logger.LogInformation("Perform {Method} Operation for archive: {Id}, Aggressive: {Aggressive}", nameof(Merge), archiveId, aggressive);
logger.LogInformation("Perform merge operation for [Archive: {Id}], [Aggressive: {Aggressive}]", archiveId, aggressive);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
AppDbContext appDbContext = scope.GetAppDbContext();
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
.AsNoTracking()
.Where(a => a.ArchiveId == archiveId)
.OrderBy(a => a.Id);
int add = 0;
int update = 0;
(int add, int update) = (0, 0);
using (IEnumerator<EntityAchievement> entityEnumerator = oldData.GetEnumerator())
using (TwoEnumerbleEnumerator<EntityAchievement, UIAFItem> enumerator = new(oldData, items))
{
using (IEnumerator<UIAFItem> uiafEnumerator = items.GetEnumerator())
(bool moveEntity, bool moveUIAF) = (true, true);
while (true)
{
bool moveEntity = true;
bool moveUIAF = true;
while (true)
if (!enumerator.MoveNext(ref moveEntity, ref moveUIAF))
{
bool moveEntityResult = moveEntity && entityEnumerator.MoveNext();
bool moveUIAFResult = moveUIAF && uiafEnumerator.MoveNext();
break;
}
if (!(moveEntityResult || moveUIAFResult))
{
break;
}
else
{
EntityAchievement? entity = entityEnumerator.Current;
UIAFItem? uiaf = uiafEnumerator.Current;
if (entity is null && uiaf is not null)
{
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
add++;
continue;
}
else if (entity is not null && uiaf is null)
{
// skip
continue;
}
(EntityAchievement? entity, UIAFItem? uiaf) = enumerator.Current;
switch (entity, uiaf)
{
case (null, not null):
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
add++;
continue;
case (not null, null):
continue; // Skipped
default:
ArgumentNullException.ThrowIfNull(entity);
ArgumentNullException.ThrowIfNull(uiaf);
if (entity.Id < uiaf.Id)
switch (entity.Id.CompareTo(uiaf.Id))
{
moveEntity = true;
moveUIAF = false;
}
else if (entity.Id == uiaf.Id)
{
moveEntity = true;
moveUIAF = true;
case < 0:
(moveEntity, moveUIAF) = (true, false);
break;
case 0:
(moveEntity, moveUIAF) = (true, true);
if (aggressive)
{
appDbContext.Achievements.RemoveAndSave(entity);
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
update++;
}
break;
case > 0:
(moveEntity, moveUIAF) = (false, true);
if (aggressive)
{
appDbContext.Achievements.RemoveAndSave(entity);
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
update++;
}
add++;
break;
}
else
{
// entity.Id > uiaf.Id
moveEntity = false;
moveUIAF = true;
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
add++;
}
}
break;
}
}
}
logger.LogInformation("{Method} Operation Complete, Add: {Add}, Update: {Update}", nameof(Merge), add, update);
logger.LogInformation("Merge operation complete, [Add: {Add}], [Update: {Update}]", add, update);
return new(add, update, 0);
}
}
/// <summary>
/// 覆盖
/// </summary>
/// <param name="archiveId">成就id</param>
/// <param name="items">待覆盖的项</param>
/// <returns>导入结果</returns>
public ImportResult Overwrite(Guid archiveId, IEnumerable<EntityAchievement> items)
{
logger.LogInformation("Perform {Method} Operation for archive: {Id}", nameof(Overwrite), archiveId);
logger.LogInformation("Perform Overwrite Operation for [Archive: {Id}]", archiveId);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
AppDbContext appDbContext = scope.GetAppDbContext();
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
.AsNoTracking()
.Where(a => a.ArchiveId == archiveId)
.OrderBy(a => a.Id);
int add = 0;
int update = 0;
int remove = 0;
(int add, int update, int remove) = (0, 0, 0);
using (IEnumerator<EntityAchievement> oldDataEnumerator = oldData.GetEnumerator())
using (TwoEnumerbleEnumerator<EntityAchievement, EntityAchievement> enumerator = new(oldData, items))
{
using (IEnumerator<EntityAchievement> newDataEnumerator = items.GetEnumerator())
(bool moveOld, bool moveNew) = (true, true);
while (true)
{
bool moveOld = true;
bool moveNew = true;
while (true)
if (!enumerator.MoveNext(ref moveOld, ref moveNew))
{
bool moveOldResult = moveOld && oldDataEnumerator.MoveNext();
bool moveNewResult = moveNew && newDataEnumerator.MoveNext();
break;
}
if (moveOldResult || moveNewResult)
{
EntityAchievement? oldEntity = oldDataEnumerator.Current;
EntityAchievement? newEntity = newDataEnumerator.Current;
if (oldEntity is null && newEntity is not null)
{
appDbContext.Achievements.AddAndSave(newEntity);
add++;
continue;
}
else if (oldEntity is not null && newEntity is null)
{
appDbContext.Achievements.RemoveAndSave(oldEntity);
remove++;
continue;
}
(EntityAchievement? oldEntity, EntityAchievement? newEntity) = enumerator.Current;
switch (oldEntity, newEntity)
{
case (null, not null):
appDbContext.Achievements.AddAndSave(newEntity);
add++;
continue;
case (not null, null):
appDbContext.Achievements.RemoveAndSave(oldEntity);
remove++;
continue;
default:
ArgumentNullException.ThrowIfNull(oldEntity);
ArgumentNullException.ThrowIfNull(newEntity);
if (oldEntity.Id < newEntity.Id)
switch (oldEntity.Id.CompareTo(newEntity.Id))
{
moveOld = true;
moveNew = false;
appDbContext.Achievements.RemoveAndSave(oldEntity);
remove++;
}
else if (oldEntity.Id == newEntity.Id)
{
moveOld = true;
moveNew = true;
case < 0:
(moveOld, moveNew) = (true, false);
break;
case 0:
(moveOld, moveNew) = (true, true);
if (oldEntity.Equals(newEntity))
{
// Skip same entry, reduce write operation.
continue;
}
else
{
appDbContext.Achievements.RemoveAndSave(oldEntity);
appDbContext.Achievements.AddAndSave(newEntity);
update++;
}
break;
case > 0:
(moveOld, moveNew) = (false, true);
if (oldEntity.Equals(newEntity))
{
// skip same entry.
continue;
}
else
{
appDbContext.Achievements.RemoveAndSave(oldEntity);
appDbContext.Achievements.AddAndSave(newEntity);
update++;
}
add++;
break;
}
else
{
// entity.Id > uiaf.Id
moveOld = false;
moveNew = true;
appDbContext.Achievements.AddAndSave(newEntity);
add++;
}
}
else
{
break;
}
}
}
}
logger.LogInformation("{Method} Operation Complete, Add: {Add}, Update: {Update}, Remove: {Remove}", nameof(Overwrite), add, update, remove);
logger.LogInformation("Overwrite Operation Complete, Add: {Add}, Update: {Update}, Remove: {Remove}", add, update, remove);
return new(add, update, remove);
}
}

View File

@@ -2,167 +2,106 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Abstraction;
using System.Collections.ObjectModel;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
namespace Snap.Hutao.Service.Achievement;
/// <summary>
/// 成就数据库服务
/// </summary>
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IAchievementDbService))]
internal sealed partial class AchievementDbService : IAchievementDbService
{
private readonly IServiceProvider serviceProvider;
public IServiceProvider ServiceProvider { get => serviceProvider; }
public Dictionary<AchievementId, EntityAchievement> GetAchievementMapByArchiveId(Guid archiveId)
{
Dictionary<AchievementId, EntityAchievement> entities;
try
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
entities = appDbContext.Achievements
.AsNoTracking()
.Where(a => a.ArchiveId == archiveId)
.ToDictionary(a => (AchievementId)a.Id);
}
return this.Query<EntityAchievement, Dictionary<AchievementId, EntityAchievement>>(query => query
.Where(a => a.ArchiveId == archiveId)
.ToDictionary(a => (AchievementId)a.Id));
}
catch (ArgumentException ex)
{
throw ThrowHelper.DatabaseCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
throw HutaoException.UserdataCorrupted(SH.ServiceAchievementUserdataCorruptedAchievementIdNotUnique, ex);
}
return entities;
}
public async ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId)
public ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.Achievements
.AsNoTracking()
return this.QueryAsync<EntityAchievement, int>(
(query, token) => query
.Where(a => a.ArchiveId == archiveId)
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
.CountAsync()
.ConfigureAwait(false);
}
.CountAsync(token),
token);
}
[SuppressMessage("", "CA1305")]
public async ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take)
public ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.Achievements
.AsNoTracking()
return this.QueryAsync<EntityAchievement, List<EntityAchievement>>(
(query, token) => query
.Where(a => a.ArchiveId == archiveId)
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
.OrderByDescending(a => a.Time.ToString())
.Take(take)
.ToListAsync()
.ConfigureAwait(false);
}
.ToListAsync(token),
token);
}
public void OverwriteAchievement(EntityAchievement achievement)
{
using (IServiceScope scope = serviceProvider.CreateScope())
this.DeleteByInnerId(achievement);
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// Delete exists one.
appDbContext.Achievements.ExecuteDeleteWhere(e => e.InnerId == achievement.InnerId);
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
{
appDbContext.Achievements.AddAndSave(achievement);
}
this.Add(achievement);
}
}
public async ValueTask OverwriteAchievementAsync(EntityAchievement achievement)
public async ValueTask OverwriteAchievementAsync(EntityAchievement achievement, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
await this.DeleteByInnerIdAsync(achievement, token).ConfigureAwait(false);
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// Delete exists one.
await appDbContext.Achievements.ExecuteDeleteWhereAsync(e => e.InnerId == achievement.InnerId).ConfigureAwait(false);
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
{
await appDbContext.Achievements.AddAndSaveAsync(achievement).ConfigureAwait(false);
}
await this.AddAsync(achievement, token).ConfigureAwait(false);
}
}
public ObservableCollection<AchievementArchive> GetAchievementArchiveCollection()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.AchievementArchives.AsNoTracking().ToObservableCollection();
}
return this.ObservableCollection<AchievementArchive>();
}
public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive)
public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// It will cascade deleted the achievements.
await appDbContext.AchievementArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false);
}
// It will cascade deleted the achievements.
await this.DeleteAsync(archive, token).ConfigureAwait(false);
}
public List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
IQueryable<EntityAchievement> result = appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId);
return [.. result];
}
return this.ListByArchiveId<EntityAchievement>(archiveId);
}
public async ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId)
public ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId, CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.Achievements
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.ToListAsync()
.ConfigureAwait(false);
}
return this.ListByArchiveIdAsync<EntityAchievement>(archiveId, token);
}
public List<AchievementArchive> GetAchievementArchiveList()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
IQueryable<AchievementArchive> result = appDbContext.AchievementArchives.AsNoTracking();
return [.. result];
}
return this.List<AchievementArchive>();
}
public async ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync()
public ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync(CancellationToken token = default)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.AchievementArchives.AsNoTracking().ToListAsync().ConfigureAwait(false);
}
return this.ListAsync<AchievementArchive>(token);
}
}

View File

@@ -1,79 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Achievement;
/// <summary>
/// 集合部分
/// </summary>
internal sealed partial class AchievementService
{
private ObservableCollection<AchievementArchive>? archiveCollection;
/// <inheritdoc/>
public AchievementArchive? CurrentArchive
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public ObservableCollection<AchievementArchive> ArchiveCollection
{
get
{
if (archiveCollection is null)
{
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
CurrentArchive = archiveCollection.SelectedOrDefault();
}
return archiveCollection;
}
}
/// <inheritdoc/>
public async ValueTask<ArchiveAddResult> AddArchiveAsync(AchievementArchive newArchive)
{
if (string.IsNullOrWhiteSpace(newArchive.Name))
{
return ArchiveAddResult.InvalidName;
}
ArgumentNullException.ThrowIfNull(archiveCollection);
// 查找是否有相同的名称
if (archiveCollection.Any(a => a.Name == newArchive.Name))
{
return ArchiveAddResult.AlreadyExists;
}
// Sync cache
await taskContext.SwitchToMainThreadAsync();
archiveCollection.Add(newArchive);
// Sync database
await taskContext.SwitchToBackgroundAsync();
CurrentArchive = newArchive;
return ArchiveAddResult.Added;
}
/// <inheritdoc/>
public async ValueTask RemoveArchiveAsync(AchievementArchive archive)
{
ArgumentNullException.ThrowIfNull(archiveCollection);
// Sync cache
await taskContext.SwitchToMainThreadAsync();
archiveCollection.Remove(archive);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await achievementDbService.RemoveAchievementArchiveAsync(archive).ConfigureAwait(false);
}
}

View File

@@ -1,64 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.InterChange.Achievement;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
namespace Snap.Hutao.Service.Achievement;
/// <summary>
/// 数据交换部分
/// </summary>
internal sealed partial class AchievementService
{
/// <inheritdoc/>
public async ValueTask<ImportResult> ImportFromUIAFAsync(AchievementArchive archive, List<UIAFItem> list, ImportStrategy strategy)
{
await taskContext.SwitchToBackgroundAsync();
Guid archiveId = archive.InnerId;
switch (strategy)
{
case ImportStrategy.AggressiveMerge:
{
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, true);
}
case ImportStrategy.LazyMerge:
{
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, false);
}
case ImportStrategy.Overwrite:
{
IEnumerable<EntityAchievement> orederedUIAF = list
.Select(uiaf => EntityAchievement.From(archiveId, uiaf))
.OrderBy(a => a.Id);
return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF);
}
default:
throw Must.NeverHappen();
}
}
/// <inheritdoc/>
public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive)
{
await taskContext.SwitchToBackgroundAsync();
List<EntityAchievement> entities = await achievementDbService
.GetAchievementListByArchiveIdAsync(archive.InnerId)
.ConfigureAwait(false);
List<UIAFItem> list = entities.SelectList(UIAFItem.From);
return new()
{
Info = UIAFInfo.From(runtimeOptions),
List = list,
};
}
}

View File

@@ -3,17 +3,16 @@
using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.Achievement;
using System.Collections.ObjectModel;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
namespace Snap.Hutao.Service.Achievement;
/// <summary>
/// 成就服务
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Scoped, typeof(IAchievementService))]
@@ -25,21 +24,127 @@ internal sealed partial class AchievementService : IAchievementService
private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
/// <inheritdoc/>
public List<AchievementView> GetAchievementViewList(AchievementArchive archive, List<MetadataAchievement> metadata)
private ObservableCollection<AchievementArchive>? archiveCollection;
public AchievementArchive? CurrentArchive
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
public ObservableCollection<AchievementArchive> ArchiveCollection
{
get
{
if (archiveCollection is null)
{
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
CurrentArchive = archiveCollection.SelectedOrDefault();
}
return archiveCollection;
}
}
public List<AchievementView> GetAchievementViewList(AchievementArchive archive, AchievementServiceMetadataContext context)
{
Dictionary<AchievementId, EntityAchievement> entities = achievementDbService.GetAchievementMapByArchiveId(archive.InnerId);
return metadata.SelectList(meta =>
return context.Achievements.SelectList(meta =>
{
EntityAchievement entity = entities.GetValueOrDefault(meta.Id) ?? EntityAchievement.From(archive.InnerId, meta.Id);
return new AchievementView(entity, meta);
});
}
/// <inheritdoc/>
public void SaveAchievement(AchievementView achievement)
{
achievementDbService.OverwriteAchievement(achievement.Entity);
}
public async ValueTask<ArchiveAddResultKind> AddArchiveAsync(AchievementArchive newArchive)
{
if (string.IsNullOrWhiteSpace(newArchive.Name))
{
return ArchiveAddResultKind.InvalidName;
}
ArgumentNullException.ThrowIfNull(archiveCollection);
if (archiveCollection.Any(a => a.Name == newArchive.Name))
{
return ArchiveAddResultKind.AlreadyExists;
}
// Sync cache
await taskContext.SwitchToMainThreadAsync();
archiveCollection.Add(newArchive);
// Sync database
await taskContext.SwitchToBackgroundAsync();
CurrentArchive = newArchive;
return ArchiveAddResultKind.Added;
}
public async ValueTask RemoveArchiveAsync(AchievementArchive archive)
{
ArgumentNullException.ThrowIfNull(archiveCollection);
// Sync cache
await taskContext.SwitchToMainThreadAsync();
archiveCollection.Remove(archive);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await achievementDbService.RemoveAchievementArchiveAsync(archive).ConfigureAwait(false);
}
public async ValueTask<ImportResult> ImportFromUIAFAsync(AchievementArchive archive, List<UIAFItem> list, ImportStrategyKind strategy)
{
await taskContext.SwitchToBackgroundAsync();
Guid archiveId = archive.InnerId;
switch (strategy)
{
case ImportStrategyKind.AggressiveMerge:
{
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, true);
}
case ImportStrategyKind.LazyMerge:
{
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
return achievementDbBulkOperation.Merge(archiveId, orederedUIAF, false);
}
case ImportStrategyKind.Overwrite:
{
IEnumerable<EntityAchievement> orederedUIAF = list
.SelectList(uiaf => EntityAchievement.From(archiveId, uiaf))
.SortBy(a => a.Id);
return achievementDbBulkOperation.Overwrite(archiveId, orederedUIAF);
}
default:
throw HutaoException.NotSupported();
}
}
public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive)
{
await taskContext.SwitchToBackgroundAsync();
List<EntityAchievement> entities = await achievementDbService
.GetAchievementListByArchiveIdAsync(archive.InnerId)
.ConfigureAwait(false);
List<UIAFItem> list = entities.SelectList(UIAFItem.From);
return new()
{
Info = UIAFInfo.From(runtimeOptions),
List = list,
};
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
namespace Snap.Hutao.Service.Achievement;
internal sealed class AchievementServiceMetadataContext : IMetadataContext,
IMetadataListAchievementSource,
IMetadataDictionaryIdAchievementSource
{
public List<MetadataAchievement> Achievements { get; set; } = default!;
public Dictionary<AchievementId, MetadataAchievement> IdAchievementMap { get; set; } = default!;
}

Some files were not shown because too many files have changed in this diff Show More