Compare commits

..

114 Commits
1.5.1 ... 1.6.2

Author SHA1 Message Date
Lightczx
e5c5c1c95c improve cloud service experience 2023-04-08 22:09:07 +08:00
DismissedLight
a4f555ea40 Merge pull request #663 from DGP-Studio/l10n_main
New Crowdin updates
2023-04-08 16:19:14 +08:00
Masterain
1ea0a14926 New translations SH.resx (English) 2023-04-08 01:17:53 -07:00
Masterain
a26b6fc528 New translations SH.resx (Chinese Traditional) 2023-04-08 01:17:52 -07:00
Masterain
e4523d540c New translations SH.resx (Korean) 2023-04-08 01:17:51 -07:00
Lightczx
6a309321f6 source generate reliquary score weight 2023-04-08 16:17:16 +08:00
Lightczx
512b1257a8 fix DbCurrent 2023-04-07 18:44:08 +08:00
Lightczx
d5b668cb8d fix cloud gacha download crash 2023-04-07 16:41:57 +08:00
Lightczx
32177491da gacha log upload 2023-04-07 15:08:20 +08:00
Lightczx
e5012d9051 improve welcome download experience 2023-04-06 15:12:51 +08:00
Chen Hill
cad1182ade Remove BITS 2023-04-05 13:03:33 +08:00
DismissedLight
e58d982e72 Merge pull request #652 from Xhichn/main
Fix hoyolab userinfo request
2023-04-05 11:12:01 +08:00
xhichn
9749c6c342 Fix hoyolab userinfo request 2023-04-05 10:02:08 +08:00
DismissedLight
179b78ca83 Launch Game QoL 2023-04-04 18:44:19 +08:00
DismissedLight
79118cdb4d fix #649 2023-04-04 16:53:01 +08:00
DismissedLight
35e7aaef4e fix HTTPS usage 2023-04-03 21:17:52 +08:00
DismissedLight
97f5904efa fix cn login 2023-03-31 13:09:03 +08:00
DismissedLight
50112cb3f6 Merge pull request #634 from Masterain98/main
Update PWA assets
2023-03-30 13:12:19 +08:00
DismissedLight
3dd7a8c85f fix HoYoLAB webview login 2023-03-30 13:07:15 +08:00
Masterain
ca06fe7b02 Remove badge logo assets 2023-03-29 17:43:57 -07:00
DismissedLight
04e9f7db72 Merge pull request #637 from DGP-Studio/l10n_main
New Crowdin updates
2023-03-29 18:41:33 +08:00
Masterain
7facf43332 New translations SH.resx (English) 2023-03-29 03:40:37 -07:00
Masterain
bc7cf93137 New translations SH.resx (Chinese Traditional) 2023-03-29 03:40:36 -07:00
Masterain
877dd93bad New translations SH.resx (Korean) 2023-03-29 03:40:35 -07:00
DismissedLight
98b3f2d202 add hutao passport 2023-03-29 17:35:43 +08:00
DismissedLight
0a7bd55dab remove using statements 2023-03-28 21:26:18 +08:00
DismissedLight
e9a8e906da Merge pull request #635 from DGP-Studio/l10n_main
New Crowdin updates
2023-03-28 18:06:03 +08:00
Masterain
93523c8832 New translations SH.resx (English) 2023-03-28 02:50:46 -07:00
Masterain
541455e51d New translations SH.resx (Chinese Traditional) 2023-03-28 02:50:45 -07:00
Masterain
cdc48a1a4f New translations SH.resx (Korean) 2023-03-28 02:50:43 -07:00
DismissedLight
ab65a62c11 fix hoyolab user creation 2023-03-28 16:43:37 +08:00
Masterain
9e90cfb1c8 Update PWA assets 2023-03-27 23:09:01 -07:00
DismissedLight
b1ace71648 fixup dependency injection 2023-03-27 20:08:00 +08:00
DismissedLight
015c731df0 Merge pull request #608 from Xhichn/main
Add basic support for hoyoverse account
2023-03-27 19:03:53 +08:00
DismissedLight
8ea4411e2a phase final 2023-03-27 19:02:09 +08:00
DismissedLight
f90b828bb4 phase 3 2023-03-27 18:39:02 +08:00
DismissedLight
97cbe7cf55 phase 2 2023-03-27 16:17:36 +08:00
DismissedLight
09abb46159 fixup some code style phase 1 2023-03-25 21:26:17 +08:00
Xhichn
523374ed3d Upload spiral abyss record for hoyolab user & clean 2023-03-24 21:44:05 +08:00
Xhichn
9decb67cff Merge branch 'DGP-Studio:main' into main 2023-03-24 20:24:37 +08:00
DismissedLight
af26c06cfc Merge pull request #623 from Masterain98/main
Update Project Information
2023-03-23 22:42:49 +08:00
DismissedLight
b550b91cc9 update banner 2023-03-23 22:24:19 +08:00
DismissedLight
2ba6377088 update bug report template 2023-03-23 12:47:01 +08:00
DismissedLight
18f8137f41 fix cookie token 2023-03-22 18:24:48 +08:00
Xhichn
32ae92e49d Login from hoyolab account page to get stoken 2023-03-22 18:13:55 +08:00
Xhichn
805fd31bf8 Login from hoyolab account website to get stoken 2023-03-22 17:44:56 +08:00
Masterain
b65e0c94f0 Update Project Information 2023-03-22 02:07:06 -07:00
DismissedLight
7e3d7e9076 remove MemoryExtension [skip ci] 2023-03-21 13:16:09 +08:00
DismissedLight
2c162d1fef fix #616 [skip ci] 2023-03-20 12:36:49 +08:00
DismissedLight
bfacf42d71 refactor user initialization 2023-03-19 21:57:49 +08:00
DismissedLight
eeee171b78 fix #611 2023-03-19 20:45:36 +08:00
DismissedLight
9861a3df37 improve fps unlocker 2023-03-19 12:27:17 +08:00
Masterain
84b9a9de23 Update azure-pipelines.yml for Azure Pipelines 2023-03-18 02:52:38 -07:00
DismissedLight
11828bd280 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-03-18 16:42:46 +08:00
DismissedLight
b29d66d6b6 fix ci build 2023-03-18 16:42:35 +08:00
Masterain
a25d1ba4ce Update azure-pipelines.yml for Azure Pipelines 2023-03-18 01:30:31 -07:00
DismissedLight
5505927ca1 fix nuget sources 2023-03-18 16:00:58 +08:00
DismissedLight
d3a60451a4 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-03-18 14:45:40 +08:00
DismissedLight
915635a843 fix material 2023-03-18 14:44:24 +08:00
Masterain
918e84be61 Update azure-pipelines.yml for Azure Pipelines 2023-03-17 19:56:09 -07:00
DismissedLight
a8a3354217 Add IsOverSea on User Entity 2023-03-17 23:20:47 +08:00
Xhichn
b6f6c5d54b Merge branch 'DGP-Studio:main' into main 2023-03-16 20:15:43 +08:00
DismissedLight
a9e0e3db39 Update README.md [skip ci] 2023-03-16 18:52:49 +08:00
DismissedLight
ece3f2cd08 migrate to CommunityToolkit.Labs.WinUI.SettingsControls 2023-03-16 18:22:30 +08:00
Xhichn
7612a2e7c0 Merge branch 'DGP-Studio:main' into main 2023-03-15 23:40:21 +08:00
Xhichn
eee34ec4c7 Merge branch 'main' of https://github.com/Xhichn/Snap.Hutao 2023-03-15 23:38:58 +08:00
Xhichn
e24a7436b4 Add daily reward claim support for hoyolab user & fix typo 2023-03-15 23:38:42 +08:00
Xhichn
a543bf3091 Fix dailynote notification for hoyoverse account, block some unsupported operations 2023-03-15 22:39:42 +08:00
DismissedLight
1d88360528 fix app options 2023-03-15 20:13:24 +08:00
DismissedLight
f21d04fadc Merge pull request #604 from GashByte/main
add 'Enabled Advanced' option to setting page
2023-03-15 19:15:02 +08:00
x3zF Love U
0cd6bf95a9 change the field name of 'EnabledAdvanced' 2023-03-15 19:12:41 +08:00
x3zF Love U
4de01d2f62 add 'Enabled Advanced' option to setting page 2023-03-15 19:04:55 +08:00
DismissedLight
05d226ad15 move viewmodels 2023-03-15 18:28:14 +08:00
Xhichn
eabf2a9ea8 Merge branch 'DGP-Studio:main' into main 2023-03-15 14:56:38 +08:00
DismissedLight
e796afbbb0 Merge pull request #603 from HolographicHat/main
Use exclusive access instead of renaming
2023-03-15 14:27:36 +08:00
Xhichn
d6099f10ad Merge branch 'DGP-Studio:main' into main 2023-03-15 13:49:00 +08:00
Xhichn
f837b39ecf Refresh gacha log by stoken is unsupported for oversea players currently 2023-03-15 13:43:03 +08:00
HolographicHat
770cabce81 Update ProcessInterop.cs 2023-03-15 00:23:14 +08:00
HolographicHat
cec4b2a23a Use exclusive access instead of renaming 2023-03-14 15:53:28 +00:00
DismissedLight
c60748626a Create .github/dependabot.yml 2023-03-14 16:33:03 +08:00
Xhichn
51d8ee5d6e Add some auth endpoints 2023-03-14 16:29:51 +08:00
Xhichn
64c020a5fb Support get avatars info from hoyolab dev calc & add some apis 2023-03-14 16:02:10 +08:00
Xhichn
934fb75fa5 Merge branch 'Globalization' 2023-03-13 21:26:18 +08:00
DismissedLight
85b9fc1a08 fix setting restart 2023-03-13 21:01:55 +08:00
Xhichn
314f86966a Remove unnecessary cookie method 2023-03-13 19:36:28 +08:00
DismissedLight
5aba2eab97 support multi-clienting 2023-03-13 19:13:35 +08:00
Xhichn
fdf2311f0c Support website login for adding hoyoverse account 2023-03-13 12:36:38 +08:00
Xhichn
b0b3553d0c Support daily notes for global server player 2023-03-13 12:16:38 +08:00
Xhichn
388cdf1848 Support sync from hoyolab my characters 2023-03-13 11:41:18 +08:00
xhichn
22f4f411ea Support spiral abyss info request for global server players 2023-03-12 23:26:00 +08:00
xhichn
b89c66fd6b Support adding hoyoverse accounts by cookie input 2023-03-12 23:25:25 +08:00
xhichn
0295d4fc22 Add client config and DS salt for oversea server request 2023-03-12 23:21:52 +08:00
xhichn
d85811ee99 Add some hoyolab endpoints 2023-03-12 23:20:09 +08:00
DismissedLight
dc8d7ac913 Merge pull request #598 from GashByte/main
Add 'Mult-Start' & fix a start game bug
2023-03-12 22:32:23 +08:00
x3zF Love U
a832ea96ea repair 'LaunchMultipleInstancesGameAsync' 2023-03-12 22:24:29 +08:00
x3zF Love U
29582efaee reduce the Task waiting time of mult-start 2023-03-12 18:15:40 +08:00
x3zF Love U
6caeb1d238 Add 'Mult-Start' & fix a start game bug 2023-03-12 16:32:59 +08:00
DismissedLight
439a8dd475 Merge pull request #594 from GashByte/main
add 'Copy' Button to 'SettingPage - Device Id'
2023-03-11 16:04:56 +08:00
x3zF Love U
60c7e65abb add 'Copy' Button to 'SettingPage - Device Id' 2023-03-11 15:42:21 +08:00
DismissedLight
5c0984b064 rescale assets [skip ci] 2023-03-11 13:19:45 +08:00
DismissedLight
9b8cce30a7 fix package logo 2023-03-11 13:02:26 +08:00
DismissedLight
463a842cb1 update assets 2023-03-10 21:48:14 +08:00
DismissedLight
8ee2908633 launch screenshot folder 2023-03-10 20:32:49 +08:00
DismissedLight
c71ecd89e3 support game resource download switch 2023-03-07 16:28:00 +08:00
DismissedLight
128b985609 fix game resource convert 2023-03-06 11:29:53 +08:00
DismissedLight
b1a03662d9 launch game impl switch monitor 2023-03-05 18:47:36 +08:00
DismissedLight
706fb3404b refactor localization 2023-03-03 16:49:27 +08:00
DismissedLight
63e1273d6a Merge pull request #582 from DGP-Studio/l10n_main
New Crowdin updates
2023-03-03 15:33:45 +08:00
Masterain
400a3b99ae New translations SH.resx (English) 2023-03-02 23:31:00 -08:00
Masterain
c0c1774db8 New translations SH.resx (Chinese Traditional) 2023-03-02 23:30:59 -08:00
Masterain
099fbf4052 New translations SH.resx (Korean) 2023-03-02 23:30:58 -08:00
Masterain
5c4405e545 Update Crowdin configuration file 2023-03-02 23:30:30 -08:00
DismissedLight
ac68579d6a fix #567 2023-02-28 12:35:49 +08:00
DismissedLight
f3387bb8c8 fix #566 2023-02-28 11:52:43 +08:00
384 changed files with 10437 additions and 7561 deletions

View File

@@ -15,20 +15,15 @@ body:
description: |-
请确保你已完整执行检查清单,否则你的 Issue 可能会被忽略
options:
- label: 完整阅读[胡桃工具箱文档](https://hut.ao/advanced/FAQ.html),并认为我的问题没有在文档中得到解答
required: true
- label: 并未完整阅读[胡桃工具箱文档](https://hut.ao/advanced/FAQ.html)
- label: 我知道文档站的导航栏中有**搜索功能**且已经搜索过相关关键词
required: true
- label: 知道文档站的导航栏中有**搜索功能**也没有搜索过相关关键词
- label: 我使用的操作系统是[受支持的版本](https://hut.ao/quick-start.html#%E6%9C%80%E4%BD%8E%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82)
required: true
- label: 我使用的操作系统是[受支持的版本](https://hut.ao/quick-start.html#%E6%9C%80%E4%BD%8E%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82)
- label: 我**通过搜索功能**确认没有其他人已经提出相同或类似的问题
required: true
- label: 没有**通过搜索功能**确认其他人提出相同或类似的问题
- label: 我明白上述的勾选项是**一个有助于快速排查问题的检查清单**,而是随手确认的选项
required: true
- label: 明白上述的勾选项是**一个有助于快速排查问题的检查清单**,而是随手确认的选项
- type: input
id: winver

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "nuget" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

4
.gitignore vendored
View File

@@ -15,5 +15,5 @@ src/Snap.Hutao/Snap.Hutao.Installer/Properties/PublishProfiles/FolderProfile.pub
src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/
src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/
src/Snap.Hutao/Snap.Hutao.Win32/bin/
src/Snap.Hutao/Snap.Hutao.Win32/obj/
src/Snap.Hutao/Snap.Hutao.Test/bin/
src/Snap.Hutao/Snap.Hutao.Test/obj/

13
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,13 @@
# Code of Conduct
> Snap Hutao is adapting the following rules to keep the community safety.
When participating in our open source community, we want all members to respect and support each other. To ensure the comfort and safety of our community members, we have established the following code of conduct:
1. Respect diversity and inclusivity. We welcome people from different countries, regions, genders, sexual orientations, abilities, religions, and cultural backgrounds to participate in our community, and we encourage respect for all differences.
2. Prohibit discrimination and harassment. We do not tolerate any form of discrimination, harassment, personal attacks, or insults. This includes but is not limited to race, gender, sexual orientation, age, religion, nationality, cultural background, physical and mental health status.
3. Respect privacy and personal information. We protect the privacy and personal information of community members and prohibit the public disclosure of any private information. If you need to disclose certain information, please make sure you have obtained the relevant person's permission.
4. Keep honesty and transparency. We expect community members to maintain honesty and transparency and not intentionally mislead or deceive others.
5. Respect community rules and other members. We encourage community members to follow community rules and guidelines and maintain a polite and respectful attitude towards other members. If you find that other members are violating community rules, please report it to community administrators or organizers in a timely manner.
The above is our community's code of conduct, and we expect all community members to abide by these rules. We will actively address behaviors that violate these rules. We believe that through mutual respect and support, we can build a friendly, inclusive, and beneficial open source community.

19
NuGet.Config Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Microsoft CsWin32" value="https://pkgs.dev.azure.com/azure-public/winsdk/_packaging/CI/nuget/v3/index.json" />
<add key="CommunityToolkit Labs" value="https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json" />
</packageSources>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<bindingRedirects>
<add key="skip" value="False" />
</bindingRedirects>
<packageManagement>
<add key="format" value="1" />
<add key="disabled" value="False" />
</packageManagement>
</configuration>

View File

@@ -1,27 +1,34 @@
![](https://repository-images.githubusercontent.com/482734649/5f8cf574-2ef0-43e9-aa8d-6cf094b54dd9)
![](https://repository-images.githubusercontent.com/482734649/c47a0a8e-868d-4d07-a66f-6d4473abfe46)
胡桃工具箱是一个 Windows 平台的开源的原神工具箱,旨在帮助玩家获得更好的游戏体验; 它是对官方移动端工具的一种非破坏性功能扩展,为不习惯在移动端进行原神游戏的 PC 玩家提供一个在 Windows 平台下获得接近移动端功能权利的途径
Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to provide a better gaming experience for players. It's an nondestructive feature extension from Genshin Impact's official mobile application, to provide similar feature on desktop, to allow PC gamers gain deserved benefits from mobile platforms.
## 下载使用 / Download
## 下载使用
[<img src="https://get.microsoft.com/images/zh-cn%20light.svg" width="30%" height="30%">](https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52)
## 贡献
## 贡献 / Contribute
* [向我们提交 PR](https://github.com/DGP-Studio/Snap.Hutao/pulls)
* [在 Crowdin 上进行本地化](https://translate.hut.ao/)
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
* [在 Crowdin 上进行本地化 / Translate project on Crowdin](https://translate.hut.ao/)
* [为我们更新文档 / Enhance our Document ](https://github.com/DGP-Studio/Snap.Hutao.Docs)
## 特别感谢
## 特别感谢 / Special Thanks
* [HolographicHat](https://github.com/HolographicHat)
* [UIGF organization](https://uigf.org)
### 特定的原神项目
### 特定的原神项目 / Specific Genshin-related Project
* [biuuu/genshin-wish-export](https://github.com/biuuu/genshin-wish-export)
* [xunkong/xunkong](https://github.com/xunkong/xunkong)
* [YuehaiTeam/cocogoat](https://github.com/YuehaiTeam/cocogoat)
### 使用的技术栈
### 使用的技术栈 / Tech Stack
* [CommunityToolkit/dotnet](https://github.com/CommunityToolkit/dotnet)
* [CommunityToolkit/Labs-Windows](https://github.com/CommunityToolkit/Labs-Windows)
* [CommunityToolkit/WindowsCommunityToolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit)
* [dahall/taskscheduler](https://github.com/dahall/taskscheduler)
* [dotnet/efcore](https://github.com/dotnet/efcore)
@@ -32,13 +39,12 @@
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
* [WinUICommunity/SettingsUI](https://github.com/WinUICommunity/SettingsUI)
### 支撑项目
### 支撑项目 / Supporter Project
* [Snap.Hutao.Server](https://github.com/DGP-Studio/Snap.Hutao.Server)
* [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)
* [Snap.Data.Mapper](https://github.com/DGP-Studio/Snap.Data.Mapper)
## 近期活跃数据
## 近期活跃数据 / Active Stat
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)

View File

@@ -65,7 +65,8 @@ steps:
inputs:
command: 'restore'
restoreSolution: '$(solution)'
feedsToUse: 'select'
feedsToUse: 'config'
nugetConfigPath: '$(Build.SourcesDirectory)/NuGet.Config'
- task: MsixPackaging@1
displayName: Build binary package
@@ -83,7 +84,7 @@ steps:
- task: MagicChunks@2
inputs:
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\AppxManifest.xml'
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\AppxManifest.xml'
fileType: 'Xml'
targetPathType: 'source'
transformationType: 'json'
@@ -104,7 +105,7 @@ steps:
mkdir Assets
mkdir Resource
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64'
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64'
- task: CopyFiles@2
@@ -112,19 +113,19 @@ steps:
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Assets'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Assets'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\Assets'
- task: CopyFiles@2
displayName: Copy Resource Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Resource'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Resource'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\Resource'
- task: CmdLine@2
displayName: Build MSIX
inputs:
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
- task: MsixSigning@1
name: signMsix
@@ -173,6 +174,7 @@ steps:
changeLogType: 'commitBased'
- task: DownloadSecureFile@1
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
name: RcloneConfigFile
displayName: Download Rclone Config
inputs:
@@ -180,6 +182,7 @@ steps:
- task: rclone@1
displayName: Upload CI via Rclone
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
inputs:
arguments: 'copy $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix downloadDGPCN:/releases/Alpha/'
configPath: '$(RcloneConfigFile.secureFilePath)'

View File

@@ -1,3 +1,3 @@
files:
- source: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.%locale%.resx
translation: /src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.%osx_locale%.resx

Binary file not shown.

BIN
res/HutaoIcon2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

BIN
res/HutaoIcon2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
res/HutaoIconSource.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
res/HutaoRepoBanner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 KiB

BIN
res/HutaoRepoBanner.psd Normal file

Binary file not shown.

3
res/LEGAL NOTICE.md Normal file
View File

@@ -0,0 +1,3 @@
本文件夹中的所有图片,均由 [DGP Studio](https://github.com/DGP-Studio) 委托 [Bilibili 画画的芦苇](https://space.bilibili.com/274422134) 绘制
Copyright ©2023 DGP Studio, All Rights Reserved.

View File

@@ -11,7 +11,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
namespace Snap.Hutao.SourceGeneration.DependencyInjection;
/// <summary>
/// 注入HttpClient代码生成器
@@ -19,11 +19,12 @@ namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
/// 防止在运行时动态查找注入类型
/// </summary>
[Generator]
public class HttpClientGenerator : ISourceGenerator
internal sealed class HttpClientGenerator : ISourceGenerator
{
private const string DefaultName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.Default";
private const string XRpcName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.XRpc";
private const string XRpc2Name = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfigration.XRpc2";
private const string DefaultName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.Default";
private const string XRpcName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.XRpc";
private const string XRpc2Name = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.XRpc2";
private const string XRpc3Name = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.XRpc3";
private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute";
private const string DynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute";
@@ -44,38 +45,38 @@ public class HttpClientGenerator : ISourceGenerator
return;
}
string toolName = this.GetGeneratorType().FullName;
StringBuilder sourceCodeBuilder = new();
sourceCodeBuilder.Append($@"// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
sourceCodeBuilder.Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
// This class is generated by Snap.Hutao.SourceGeneration
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Net.Http;
namespace Snap.Hutao.Core.DependencyInjection;
internal static partial class IocHttpClientConfiguration
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(HttpClientGenerator)}}","1.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial IServiceCollection AddHttpClients(this IServiceCollection services)
{
""");
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Net.Http;
FillWithHttpClients(receiver, sourceCodeBuilder);
namespace Snap.Hutao.Core.DependencyInjection;
sourceCodeBuilder.Append("""
internal static partial class IocHttpClientConfiguration
{{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{toolName}"",""1.0.0.0"")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial IServiceCollection AddHttpClients(this IServiceCollection services)
{{");
FillWithInjectionServices(receiver, sourceCodeBuilder);
sourceCodeBuilder.Append(@"
return services;
}
}");
return services;
}
}
""");
context.AddSource("IocHttpClientConfiguration.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
}
private static void FillWithInjectionServices(HttpClientSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
private static void FillWithHttpClients(HttpClientSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
{
List<string> lines = new();
StringBuilder lineBuilder = new();
@@ -84,16 +85,23 @@ internal static partial class IocHttpClientConfiguration
{
lineBuilder.Clear().Append("\r\n");
lineBuilder.Append(@" services.AddHttpClient<");
lineBuilder.Append($"{classSymbol.ToDisplayString()}>(");
AttributeData httpClientInfo = classSymbol
.GetAttributes()
.Single(attr => attr.AttributeClass!.ToDisplayString() == HttpClientSyntaxContextReceiver.AttributeName);
ImmutableArray<TypedConstant> arguments = httpClientInfo.ConstructorArguments;
TypedConstant injectAs = arguments[0];
if (arguments.Length == 2)
{
TypedConstant interfaceType = arguments[1];
lineBuilder.Append($"{interfaceType.Value}, ");
}
string injectAsName = injectAs.ToCSharpString();
TypedConstant configuration = arguments[0];
lineBuilder.Append($"{classSymbol.ToDisplayString()}>(");
string injectAsName = configuration.ToCSharpString();
switch (injectAsName)
{
case DefaultName:
@@ -105,8 +113,11 @@ internal static partial class IocHttpClientConfiguration
case XRpc2Name:
lineBuilder.Append("XRpc2Configuration)");
break;
case XRpc3Name:
lineBuilder.Append("XRpc3Configuration)");
break;
default:
throw new InvalidOperationException($"非法的HttpClientConfigration值: [{injectAsName}]");
throw new InvalidOperationException($"非法的 HttpClientConfiguration 值: [{injectAsName}]");
}
AttributeData? handlerInfo = classSymbol
@@ -120,11 +131,11 @@ internal static partial class IocHttpClientConfiguration
foreach (KeyValuePair<string, TypedConstant> property in properties)
{
lineBuilder.Append(" ");
lineBuilder.Append(' ');
lineBuilder.Append(property.Key);
lineBuilder.Append(" = ");
lineBuilder.Append(property.Value.ToCSharpString());
lineBuilder.Append(",");
lineBuilder.Append(',');
}
lineBuilder.Append(" })");
@@ -135,7 +146,7 @@ internal static partial class IocHttpClientConfiguration
lineBuilder.Append(".AddHttpMessageHandler<DynamicSecretHandler>()");
}
lineBuilder.Append(";");
lineBuilder.Append(';');
lines.Add(lineBuilder.ToString());
}

View File

@@ -11,7 +11,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
namespace Snap.Hutao.SourceGeneration.DependencyInjection;
/// <summary>
/// 注入代码生成器
@@ -19,11 +19,12 @@ namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
/// 防止在运行时动态查找注入类型
/// </summary>
[Generator]
public class InjectionGenerator : ISourceGenerator
internal sealed class InjectionGenerator : ISourceGenerator
{
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
private const string CRLF = "\r\n";
/// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context)
@@ -41,30 +42,28 @@ public class InjectionGenerator : ISourceGenerator
return;
}
string toolName = this.GetGeneratorType().FullName;
StringBuilder sourceCodeBuilder = new();
sourceCodeBuilder.Append($@"// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
sourceCodeBuilder.Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
// This class is generated by Snap.Hutao.SourceGeneration
using Microsoft.Extensions.DependencyInjection;
namespace Snap.Hutao.Core.DependencyInjection;
internal static partial class ServiceCollectionExtension
{{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{toolName}"",""1.0.0.0"")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial IServiceCollection AddInjections(this IServiceCollection services)
{{");
namespace Snap.Hutao.Core.DependencyInjection;
internal static partial class ServiceCollectionExtension
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(InjectionGenerator)}}","1.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial IServiceCollection AddInjections(this IServiceCollection services)
{
""");
FillWithInjectionServices(receiver, sourceCodeBuilder);
sourceCodeBuilder.Append(@"
return services;
}
}");
sourceCodeBuilder.Append("""
return services;
}
}
""");
context.AddSource("ServiceCollectionExtension.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
}
@@ -76,46 +75,42 @@ internal static partial class ServiceCollectionExtension
foreach (INamedTypeSymbol classSymbol in receiver.Classes)
{
IEnumerable<AttributeData> datas = classSymbol
AttributeData injectionInfo = classSymbol
.GetAttributes()
.Where(attr => attr.AttributeClass!.ToDisplayString() == InjectionSyntaxContextReceiver.AttributeName);
.Single(attr => attr.AttributeClass!.ToDisplayString() == InjectionSyntaxContextReceiver.AttributeName);
foreach (AttributeData injectionInfo in datas)
lineBuilder
.Clear()
.Append(CRLF);
ImmutableArray<TypedConstant> arguments = injectionInfo.ConstructorArguments;
TypedConstant injectAs = arguments[0];
string injectAsName = injectAs.ToCSharpString();
switch (injectAsName)
{
lineBuilder
.Clear()
.Append("\r\n");
ImmutableArray<TypedConstant> arguments = injectionInfo.ConstructorArguments;
TypedConstant injectAs = arguments[0];
string injectAsName = injectAs.ToCSharpString();
switch (injectAsName)
{
case InjectAsSingletonName:
lineBuilder.Append(@" services.AddSingleton(");
break;
case InjectAsTransientName:
lineBuilder.Append(@" services.AddTransient(");
break;
case InjectAsScopedName:
lineBuilder.Append(@" services.AddScoped(");
break;
default:
throw new InvalidOperationException($"非法的 InjectAs 值: [{injectAsName}]");
}
if (arguments.Length == 2)
{
TypedConstant interfaceType = arguments[1];
lineBuilder.Append($"{interfaceType.ToCSharpString()}, ");
}
lineBuilder.Append($"typeof({classSymbol.ToDisplayString()}));");
lines.Add(lineBuilder.ToString());
case InjectAsSingletonName:
lineBuilder.Append(@" services.AddSingleton<");
break;
case InjectAsTransientName:
lineBuilder.Append(@" services.AddTransient<");
break;
case InjectAsScopedName:
lineBuilder.Append(@" services.AddScoped<");
break;
default:
throw new InvalidOperationException($"非法的 InjectAs 值: [{injectAsName}]");
}
if (arguments.Length == 2)
{
TypedConstant interfaceType = arguments[1];
lineBuilder.Append($"{interfaceType.Value}, ");
}
lineBuilder.Append($"{classSymbol.ToDisplayString()}>();");
lines.Add(lineBuilder.ToString());
}
foreach (string line in lines.OrderBy(x => x))
@@ -153,4 +148,4 @@ internal static partial class ServiceCollectionExtension
}
}
}
}
}

View File

@@ -0,0 +1,439 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
namespace Snap.Hutao.SourceGeneration;
// Really simple JSON parser in ~300 lines
// - Attempts to parse JSON files with minimal GC allocation
// - Nice and simple "[1,2,3]".FromJson<List<int>>() API
// - Classes and structs can be parsed too!
// class Foo { public int Value; }
// "{\"Value\":10}".FromJson<Foo>()
// - Can parse JSON without type information into Dictionary<string,object> and List<object> e.g.
// "[1,2,3]".FromJson<object>().GetType() == typeof(List<object>)
// "{\"Value\":10}".FromJson<object>().GetType() == typeof(Dictionary<string,object>)
// - No JIT Emit support to support AOT compilation on iOS
// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead.
// - Only public fields and property setters on classes/structs will be written to
//
// Limitations:
// - No JIT Emit support to parse structures quickly
// - Limited to parsing <2GB JSON files (due to int.MaxValue)
// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception.
public static class JsonParser
{
[ThreadStatic]
private static Stack<List<string>>? splitArrayPool;
[ThreadStatic]
private static StringBuilder? stringBuilder;
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, FieldInfo>>? fieldInfoCache;
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, PropertyInfo>>? propertyInfoCache;
public static T? FromJson<T>(this string json)
{
// Initialize, if needed, the ThreadStatic variables
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
stringBuilder ??= new StringBuilder();
splitArrayPool ??= new Stack<List<string>>();
//Remove all whitespace not within strings to make parsing simpler
stringBuilder.Length = 0;
for (int i = 0; i < json.Length; i++)
{
char c = json[i];
if (c == '"')
{
i = AppendUntilStringEnd(true, i, json);
continue;
}
if (char.IsWhiteSpace(c))
{
continue;
}
stringBuilder.Append(c);
}
//Parse the thing!
return (T?)ParseValue(typeof(T), stringBuilder.ToString());
}
private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
{
stringBuilder!.Append(json[startIdx]);
for (int i = startIdx + 1; i < json.Length; i++)
{
if (json[i] == '\\')
{
if (appendEscapeCharacter)
{
stringBuilder.Append(json[i]);
}
stringBuilder.Append(json[i + 1]);
i++;//Skip next character as it is escaped
}
else if (json[i] == '"')
{
stringBuilder.Append(json[i]);
return i;
}
else
{
stringBuilder.Append(json[i]);
}
}
return json.Length - 1;
}
//Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
private static List<string> Split(string json)
{
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List<string>();
splitArray.Clear();
if (json.Length == 2)
{
return splitArray;
}
int parseDepth = 0;
stringBuilder!.Length = 0;
for (int i = 1; i < json.Length - 1; i++)
{
switch (json[i])
{
case '[':
case '{':
parseDepth++;
break;
case ']':
case '}':
parseDepth--;
break;
case '"':
i = AppendUntilStringEnd(true, i, json);
continue;
case ',':
case ':':
if (parseDepth == 0)
{
splitArray.Add(stringBuilder.ToString());
stringBuilder.Length = 0;
continue;
}
break;
}
stringBuilder.Append(json[i]);
}
splitArray.Add(stringBuilder.ToString());
return splitArray;
}
internal static object? ParseValue(Type type, string json)
{
if (type == typeof(string))
{
if (json.Length <= 2)
{
return string.Empty;
}
StringBuilder parseStringBuilder = new(json.Length);
for (int i = 1; i < json.Length - 1; ++i)
{
if (json[i] == '\\' && i + 1 < json.Length - 1)
{
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
if (j >= 0)
{
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
++i;
continue;
}
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
{
if (uint.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c))
{
parseStringBuilder.Append((char)c);
i += 5;
continue;
}
}
}
parseStringBuilder.Append(json[i]);
}
return parseStringBuilder.ToString();
}
if (type.IsPrimitive)
{
object result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
return result;
}
if (type == typeof(decimal))
{
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result);
return result;
}
if (type == typeof(DateTime))
{
DateTime.TryParse(json.Replace("\"", ""), System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime result);
return result;
}
if (json == "null")
{
return null;
}
if (type.IsEnum)
{
if (json[0] == '"')
{
json = json.Substring(1, json.Length - 2);
}
try
{
return Enum.Parse(type, json, false);
}
catch
{
return 0;
}
}
if (type.IsArray)
{
Type arrayType = type.GetElementType();
if (json[0] != '[' || json[json.Length - 1] != ']')
{
return null;
}
List<string> elems = Split(json);
Array newArray = Array.CreateInstance(arrayType, elems.Count);
for (int i = 0; i < elems.Count; i++)
{
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
}
splitArrayPool!.Push(elems);
return newArray;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
Type listType = type.GetGenericArguments()[0];
if (json[0] != '[' || json[json.Length - 1] != ']')
{
return null;
}
List<string> elems = Split(json);
IList list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
for (int i = 0; i < elems.Count; i++)
{
list.Add(ParseValue(listType, elems[i]));
}
splitArrayPool!.Push(elems);
return list;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
Type keyType, valueType;
{
Type[] args = type.GetGenericArguments();
keyType = args[0];
valueType = args[1];
}
//Refuse to parse dictionary keys that aren't of type string
if (keyType != typeof(string))
{
return null;
}
//Must be a valid dictionary element
if (json[0] != '{' || json[json.Length - 1] != '}')
{
return null;
}
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return null;
}
IDictionary dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
{
continue;
}
string keyValue = elems[i].Substring(1, elems[i].Length - 2);
object? val = ParseValue(valueType, elems[i + 1]);
dictionary[keyValue] = val;
}
return dictionary;
}
if (type == typeof(object))
{
return ParseAnonymousValue(json);
}
if (json[0] == '{' && json[json.Length - 1] == '}')
{
return ParseObject(type, json);
}
return null;
}
private static object? ParseAnonymousValue(string json)
{
if (json.Length == 0)
{
return null;
}
if (json[0] == '{' && json[json.Length - 1] == '}')
{
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return null;
}
Dictionary<string, object?> dict = new Dictionary<string, object?>(elems.Count / 2);
for (int i = 0; i < elems.Count; i += 2)
{
dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]);
}
return dict;
}
if (json[0] == '[' && json[json.Length - 1] == ']')
{
List<string> items = Split(json);
List<object?> finalList = new(items.Count);
for (int i = 0; i < items.Count; i++)
{
finalList.Add(ParseAnonymousValue(items[i]));
}
return finalList;
}
if (json[0] == '"' && json[json.Length - 1] == '"')
{
string str = json.Substring(1, json.Length - 2);
return str.Replace("\\", string.Empty);
}
if (char.IsDigit(json[0]) || json[0] == '-')
{
if (json.Contains("."))
{
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result);
return result;
}
else
{
int.TryParse(json, out int result);
return result;
}
}
if (json == "true")
{
return true;
}
if (json == "false")
{
return false;
}
// handles json == "null" as well as invalid JSON
return null;
}
private static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
{
Dictionary<string, T> nameToMember = new(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < members.Length; i++)
{
T member = members[i];
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
{
continue;
}
string name = member.Name;
if (member.IsDefined(typeof(DataMemberAttribute), true))
{
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
{
name = dataMemberAttribute.Name;
}
}
nameToMember.Add(name, member);
}
return nameToMember;
}
private static object ParseObject(Type type, string json)
{
object instance = FormatterServices.GetUninitializedObject(type);
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
List<string> elems = Split(json);
if (elems.Count % 2 != 0)
{
return instance;
}
if (!fieldInfoCache!.TryGetValue(type, out Dictionary<string, FieldInfo> nameToField))
{
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
fieldInfoCache.Add(type, nameToField);
}
if (!propertyInfoCache!.TryGetValue(type, out Dictionary<string, PropertyInfo> nameToProperty))
{
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
propertyInfoCache.Add(type, nameToProperty);
}
for (int i = 0; i < elems.Count; i += 2)
{
if (elems[i].Length <= 2)
{
continue;
}
string key = elems[i].Substring(1, elems[i].Length - 2);
string value = elems[i + 1];
if (nameToField.TryGetValue(key, out FieldInfo fieldInfo))
{
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
}
else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
}
}
return instance;
}
}

View File

@@ -0,0 +1,139 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
namespace Snap.Hutao.SourceGeneration;
[Generator]
internal sealed class ReliquaryWeightConfigurationGenerator : ISourceGenerator
{
private const string ReliquaryWeightConfigurationFileName = "ReliquaryWeightConfiguration.json";
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
try
{
AdditionalText configurationJsonFile = context.AdditionalFiles
.First(af => Path.GetFileName(af.Path) == ReliquaryWeightConfigurationFileName);
string configurationJson = configurationJsonFile.GetText(context.CancellationToken)!.ToString();
Dictionary<string, ReliquaryWeightConfigurationMetadata> metadataMap =
JsonParser.FromJson<Dictionary<string, ReliquaryWeightConfigurationMetadata>>(configurationJson)!;
StringBuilder sourceCodeBuilder = new();
sourceCodeBuilder.Append($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 圣遗物评分权重配置
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(ReliquaryWeightConfigurationGenerator)}}","1.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
internal static class ReliquaryWeightConfiguration
{
/// <summary>
/// 默认
/// </summary>
public static readonly AffixWeight Default = new(0, 100, 75, 0, 100, 100, 0, 55, 0);
/// <summary>
/// 词条权重
/// </summary>
public static readonly List<AffixWeight> AffixWeights = new()
{
""");
foreach (KeyValuePair<string, ReliquaryWeightConfigurationMetadata> kvp in metadataMap.OrderBy(kvp => kvp.Key))
{
AppendAffixWeight(sourceCodeBuilder, kvp.Key, kvp.Value);
}
sourceCodeBuilder.Append($$"""
};
}
""");
context.AddSource("ReliquaryWeightConfiguration.g.cs", SourceText.From(sourceCodeBuilder.ToString(), Encoding.UTF8));
}
catch (Exception ex)
{
context.AddSource("ReliquaryWeightConfiguration.g.cs", ex.ToString());
}
}
private void AppendAffixWeight(StringBuilder builder, string id, ReliquaryWeightConfigurationMetadata metadata)
{
StringBuilder lineBuilder = new StringBuilder()
.Append(" new AffixWeight(").Append(id).Append(',')
.Append(' ').Append(metadata.Hp).Append(',')
.Append(' ').Append(metadata.Attack).Append(',')
.Append(' ').Append(metadata.Defense).Append(',')
.Append(' ').Append(metadata.CritRate).Append(',')
.Append(' ').Append(metadata.CritHurt).Append(',')
.Append(' ').Append(metadata.Mastery).Append(',')
.Append(' ').Append(metadata.Recharge).Append(',')
.Append(' ').Append(metadata.Healing).Append(')')
.Append('.').Append(metadata.ElementType).Append("(").Append(metadata.ElementHurt).Append(')');
if (metadata.PhysicialHurt != 0)
{
lineBuilder.Append(".Physical(").Append(metadata.PhysicialHurt).Append(')');
}
lineBuilder.Append(',');
builder.AppendLine(lineBuilder.ToString());
}
private sealed class ReliquaryWeightConfigurationMetadata
{
[DataMember(Name = "hp")]
public int Hp { get; set; }
[DataMember(Name = "atk")]
public int Attack { get; set; }
[DataMember(Name = "def")]
public int Defense { get; set; }
[DataMember(Name = "cpct")]
public int CritRate { get; set; }
[DataMember(Name = "cdmg")]
public int CritHurt { get; set; }
[DataMember(Name = "mastery")]
public int Mastery { get; set; }
[DataMember(Name = "recharge")]
public int Recharge { get; set; }
[DataMember(Name = "heal")]
public int Healing { get; set; }
[DataMember(Name = "element")]
public string ElementType { get; set; } = default!;
[DataMember(Name = "dmg")]
public int ElementHurt { get; set; }
[DataMember(Name = "phy")]
public int PhysicialHurt { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@@ -18,11 +18,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4-beta1.22518.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.SourceGeneration;
/// 类型应为内部分析器
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class TypeInternalAnalyzer : DiagnosticAnalyzer
internal sealed class TypeInternalAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor typeInternalDescriptor = new("SH001", "Type should be internal", "Type should be internal", "Quality", DiagnosticSeverity.Info, true);
@@ -59,4 +59,4 @@ internal class TypeInternalAnalyzer : DiagnosticAnalyzer
classSyntax.ReportDiagnostic(diagnostic);
}
}
}
}

View File

@@ -0,0 +1,25 @@
namespace Snap.Hutao.Test;
[TestClass]
public class CSharpLanguageFeatureTest
{
[TestMethod]
public unsafe void NullStringFixedAlsoNullPointer()
{
string testStr = null!;
fixed(char* pStr = testStr)
{
Assert.IsTrue(pStr == null);
}
}
[TestMethod]
public unsafe void EmptyStringFixedIsNullTerminator()
{
string testStr = string.Empty;
fixed (char* pStr = testStr)
{
Assert.IsTrue(*pStr == '\0');
}
}
}

View File

@@ -0,0 +1,57 @@
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Snap.Hutao.Test;
[TestClass]
public class DependencyInjectionTest
{
[TestMethod]
public void OriginalTypeNotDiscoverable()
{
IServiceProvider services = new ServiceCollection()
.AddSingleton<IService, ServiceA>()
.AddSingleton<IService, ServiceB>()
.BuildServiceProvider();
Assert.IsNull(services.GetService<ServiceA>());
Assert.IsNull(services.GetService<ServiceB>());
}
[TestMethod]
public void ScopedServiceInitializeMultipleTimesInScope()
{
IServiceProvider services = new ServiceCollection()
.AddScoped<IService, ServiceA>()
.BuildServiceProvider();
IServiceScopeFactory scopeFactory = services.GetRequiredService<IServiceScopeFactory>();
using (IServiceScope scope = scopeFactory.CreateScope())
{
IService service1 = scope.ServiceProvider.GetRequiredService<IService>();
IService service2 = scope.ServiceProvider.GetRequiredService<IService>();
Assert.AreNotEqual(service1.Id, service2.Id);
}
}
private interface IService
{
Guid Id { get; }
}
private sealed class ServiceA : IService
{
public Guid Id
{
get => Guid.NewGuid();
}
}
private sealed class ServiceB : IService
{
public Guid Id
{
get => throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting;

View File

@@ -0,0 +1,26 @@
using System.Text.Json;
namespace Snap.Hutao.Test;
[TestClass]
public class JsonSerializeTest
{
private const string SmapleObjectJson = """
{
"A" :1
}
""";
[TestMethod]
public void DelegatePropertyCanSerialize()
{
Sample sample = JsonSerializer.Deserialize<Sample>(SmapleObjectJson)!;
Assert.AreEqual(sample.B, 1);
}
private class Sample
{
public int A { get => B; set => B = value; }
public int B { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -64,6 +66,22 @@ Global
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|arm64.ActiveCfg = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|arm64.Build.0 = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|x64.ActiveCfg = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|x64.Build.0 = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|x86.ActiveCfg = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|x86.Build.0 = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|Any CPU.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|arm64.ActiveCfg = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|arm64.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x64.ActiveCfg = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x64.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.ActiveCfg = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -10,7 +10,6 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<muxc:XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///SettingsUI/Themes/Generic.xaml"/>
<ResourceDictionary Source="Control/Theme/FontStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
@@ -30,8 +29,9 @@
<!-- Page Transparent Background -->
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- InfoBar Resource -->
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
<Thickness x:Key="InfoBarIconMargin">19,16,19,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">0,0,0,0</Thickness>
<x:Double x:Key="InfoBarIconFontSize">20</x:Double>
<!-- Pivot Resource -->
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
@@ -48,11 +48,23 @@
<GridLength x:Key="CompatGridLength2">268</GridLength>
<!-- Brushes -->
<SolidColorBrush x:Key="AvatarPropertyAddValueBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
<!-- Settings -->
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
<Style
x:Key="SettingsSectionHeaderTextBlockStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
TargetType="TextBlock">
<Style.Setters>
<Setter Property="Margin" Value="1,29,0,5"/>
</Style.Setters>
</Style>
<!-- Uris -->
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
<x:String x:Key="DocumentLink_BugReport">https://hut.ao/statements/bug-report.html</x:String>
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
<x:String x:Key="Sponsor_Afadian">https://afdian.net/a/DismissedLight</x:String>
<x:String x:Key="UI_ItemIcon_None">https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png</x:String>
@@ -66,6 +78,7 @@
<!-- Converters -->
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<cwuc:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
@@ -90,6 +103,7 @@
<shvc:EmptyObjectToVisibilityRevertConverter x:Key="EmptyObjectToVisibilityRevertConverter"/>
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
<shvc:StringBoolConverter x:Key="StringBoolConverter"/>
<!-- Styles -->
<Style

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

View File

@@ -28,4 +28,9 @@ internal static class BoxedValues
/// <see cref="true"/>
/// </summary>
public static readonly object True = true;
/// <summary>
/// <see cref="false"/>
/// </summary>
public static readonly object False = false;
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Animations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using Snap.Hutao.Service.Navigation;

View File

@@ -1,10 +1,7 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wsc="using:WinUICommunity.SettingsUI.Controls">
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<FontFamily x:Key="MiSans">ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
<FontFamily x:Key="CascadiaMonoAndMiSans">ms-appx:///Resource/Font/CascadiaMono.ttf#Cascadia Mono, ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
@@ -84,6 +81,13 @@
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}"/>
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}"/>
</Style>
<Style BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}" TargetType="MenuFlyoutItem">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
@@ -99,112 +103,4 @@
<Style TargetType="InfoBar">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultSettingStyle}" TargetType="wsc:Setting"/>
<Style x:Key="DefaultSettingStyle" TargetType="wsc:Setting">
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Margin" Value="0,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="wsc:Setting">
<Grid
x:Name="RootGrid"
MinHeight="48"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- Icon -->
<ColumnDefinition Width="*"/>
<!-- Header and subtitle -->
<ColumnDefinition Width="Auto"/>
<!-- Action control -->
</Grid.ColumnDefinitions>
<ContentPresenter
x:Name="IconPresenter"
MaxWidth="20"
Margin="2,0,18,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Icon}"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
IsTextScaleFactorEnabled="False"/>
<StackPanel
Grid.Column="1"
Margin="0,0,16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<TextBlock
x:Name="HeaderPresenter"
VerticalAlignment="Center"
FontFamily="{StaticResource MiSans}"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
Text="{TemplateBinding Header}"/>
<ContentPresenter
x:Name="DescriptionPresenter"
Content="{TemplateBinding Description}"
FontFamily="{StaticResource MiSans}"
FontSize="{StaticResource SecondaryTextFontSize}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="WrapWholeWords">
<ContentPresenter.Resources>
<Style BasedOn="{StaticResource CaptionTextBlockStyle}" TargetType="TextBlock">
<Style.Setters>
<Setter Property="TextWrapping" Value="WrapWholeWords"/>
</Style.Setters>
</Style>
<Style BasedOn="{StaticResource TextButtonStyle}" TargetType="HyperlinkButton">
<Style.Setters>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="0,0,0,0"/>
</Style.Setters>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
<ContentPresenter
x:Name="ContentPresenter"
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding ActionContent}"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="HeaderPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="DescriptionPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="IconPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -18,7 +18,7 @@ namespace Snap.Hutao.Core.Caching;
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton, typeof(IImageCache))]
[HttpClient(HttpClientConfigration.Default)]
[HttpClient(HttpClientConfiguration.Default)]
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 8)]
internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
{

View File

@@ -24,16 +24,31 @@ internal static class CoreEnvironment
/// </summary>
public const string HoyolabUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{HoyolabXrpcVersion}";
/// <summary>
/// Hoyolab请求UA
/// </summary>
public const string HoyolabOsUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBSOversea/{HoyolabOsXrpcVersion}";
/// <summary>
/// 米游社移动端请求UA
/// </summary>
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{HoyolabXrpcVersion}";
/// <summary>
/// Hoyolab 移动端请求UA
/// </summary>
public const string HoyolabOsMobileUA = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBSOversea/{HoyolabOsXrpcVersion}";
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string HoyolabXrpcVersion = "2.44.1";
/// <summary>
/// Hoyolab Rpc 版本
/// </summary>
public const string HoyolabOsXrpcVersion = "2.28.0";
/// <summary>
/// 盐
/// </summary>
@@ -45,6 +60,9 @@ internal static class CoreEnvironment
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
// This SALT is not reliable
[SaltType.OSK2] = "6cqshh5dhw73bzxn20oexa9k516chk7s",
}.ToImmutableDictionary();
/// <summary>

View File

@@ -42,7 +42,7 @@ internal sealed class DbCurrent<TEntity, TMessage>
set
{
// prevent useless sets
if (current == value)
if (current?.InnerId == value?.InnerId)
{
return;
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Database;
/// <summary>
@@ -15,6 +17,7 @@ internal static class EnumerableExtension
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>
/// <returns>选中的值或默认值</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
where TSource : ISelectable
{

View File

@@ -12,6 +12,11 @@ namespace Snap.Hutao.Core.Database;
[HighQuality]
internal interface ISelectable
{
/// <summary>
/// 数据库内部Id
/// </summary>
Guid InnerId { get; }
/// <summary>
/// 获取或设置当前项的选中状态
/// </summary>

View File

@@ -3,7 +3,6 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Snap.Hutao.Core.Database;

View File

@@ -1,94 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Core.Database;
/// <summary>
/// 设置帮助类
/// </summary>
[HighQuality]
internal static class SettingEntryExtension
{
/// <summary>
/// 获取或添加一个对应的设置
/// </summary>
/// <param name="dbSet">设置集</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>设置</returns>
public static SettingEntry SingleOrAdd(this DbSet<SettingEntry> dbSet, string key, string value)
{
SettingEntry? entry = dbSet.SingleOrDefault(entry => key == entry.Key);
if (entry == null)
{
entry = new(key, value);
dbSet.AddAndSave(entry);
}
return entry;
}
/// <summary>
/// 获取或添加一个对应的设置
/// </summary>
/// <param name="dbSet">设置集</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>设置</returns>
public static async Task<SettingEntry> SingleOrAddAsync(this DbSet<SettingEntry> dbSet, string key, string value)
{
SettingEntry? entry = await dbSet.SingleOrDefaultAsync(entry => key == entry.Key).ConfigureAwait(false);
if (entry == null)
{
entry = new(key, value);
await dbSet.AddAndSaveAsync(entry).ConfigureAwait(false);
}
return entry;
}
/// <summary>
/// 获取 Boolean 值
/// </summary>
/// <param name="entry">设置</param>
/// <returns>值</returns>
public static bool GetBoolean(this SettingEntry entry)
{
return bool.Parse(entry.Value!);
}
/// <summary>
/// 设置 Boolean 值
/// </summary>
/// <param name="entry">设置</param>
/// <param name="value">值</param>
public static void SetBoolean(this SettingEntry entry, bool value)
{
entry.Value = value.ToString();
}
/// <summary>
/// 获取 Int32 值
/// </summary>
/// <param name="entry">设置</param>
/// <returns>值</returns>
public static int GetInt32(this SettingEntry entry)
{
return int.Parse(entry.Value!);
}
/// <summary>
/// 设置 Int32 值
/// </summary>
/// <param name="entry">设置</param>
/// <param name="value">值</param>
public static void SetInt32(this SettingEntry entry, int value)
{
entry.Value = value.ToString();
}
}

View File

@@ -15,7 +15,16 @@ internal sealed class HttpClientAttribute : Attribute
/// 构造一个新的特性
/// </summary>
/// <param name="configration">配置</param>
public HttpClientAttribute(HttpClientConfigration configration)
public HttpClientAttribute(HttpClientConfiguration configration)
{
}
/// <summary>
/// 构造一个新的特性
/// </summary>
/// <param name="configration">配置</param>
/// <param name="interfaceType">实现的接口类型</param>
public HttpClientAttribute(HttpClientConfiguration configration, Type interfaceType)
{
}
}

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
/// Http客户端配置
/// </summary>
[HighQuality]
internal enum HttpClientConfigration
internal enum HttpClientConfiguration
{
/// <summary>
/// 默认配置
@@ -23,4 +23,9 @@ internal enum HttpClientConfigration
/// 米游社登录请求配置
/// </summary>
XRpc2,
/// <summary>
/// 国际服Hoyolab请求配置
/// </summary>
XRpc3,
}

View File

@@ -15,7 +15,7 @@ internal static class CastableServiceExtension
/// <typeparam name="T">目标转换类型</typeparam>
/// <param name="service">对象</param>
/// <returns>转换类型后的对象</returns>
public static T? ImplictAs<T>(this ICastableService service)
public static T? As<T>(this ICastableService service)
where T : class
{
return service as T;

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.DependencyInjection;
/// <summary>
/// 服务集合扩展
/// </summary>
internal static class EnumerableServiceExtension
{
/// <summary>
/// 选择对应的服务
/// </summary>
/// <typeparam name="TService">服务类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="name">名称</param>
/// <returns>对应的服务</returns>
public static TService Pick<TService>(this IEnumerable<TService> services, string name)
where TService : INamedService
{
return services.Single(s => s.Name == name);
}
/// <summary>
/// 选择对应的服务
/// </summary>
/// <typeparam name="TService">服务类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="isOversea">是否为海外服/Hoyolab</param>
/// <returns>对应的服务</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TService Pick<TService>(this IEnumerable<TService> services, bool isOversea)
where TService : IOverseaSupport
{
return services.Single(s => s.IsOversea == isOversea);
}
/// <summary>
/// 选择对应的服务
/// </summary>
/// <typeparam name="TService">服务类型</typeparam>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="isOversea">是否为海外服/Hoyolab</param>
/// <returns>对应的服务</returns>
public static TService PickRequiredService<TService>(this IServiceProvider serviceProvider, bool isOversea)
where TService : IOverseaSupport
{
return serviceProvider.GetRequiredService<IEnumerable<TService>>().Pick(isOversea);
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Core.DependencyInjection;
/// <summary>
/// 有名称的对象

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