Compare commits

..

101 Commits

Author SHA1 Message Date
qhy040404
ceb26bb669 impl #1334 2024-02-01 22:19:35 +08:00
Lightczx
a4e782da78 refine #1316 2024-02-01 16:17:49 +08:00
Lightczx
d5551e5cdf fix #1316 2024-02-01 14:30:50 +08:00
Lightczx
f016a4a27f refine #1347 2024-02-01 09:42:55 +08:00
DismissedLight
8b931b6d89 Merge pull request #1349 from DGP-Studio/fix/1347
fix #1347
2024-02-01 09:23:40 +08:00
Lightczx
b942ceb914 codestyle 2024-02-01 09:22:52 +08:00
qhy040404
f7a49e52e0 fix #1347 2024-02-01 00:10:23 +08:00
qhy040404
d4bd610fe2 temporary fix qr login
let's play zzz :(
2024-01-31 23:00:07 +08:00
Lightczx
a3dcfd3804 metadata special name handling 2024-01-31 17:25:43 +08:00
Lightczx
592525d149 typo fix for #1344 2024-01-31 14:20:57 +08:00
Lightczx
83c4598df5 fix #1333 again 2024-01-31 11:37:48 +08:00
DismissedLight
aa680388ad refine ui 2024-01-30 21:59:42 +08:00
Lightczx
ba4f59de30 update dynamic proxy injection 2024-01-30 17:31:23 +08:00
Lightczx
8780cf385e move class 2024-01-30 15:58:18 +08:00
Lightczx
431cdd1253 refine achievement page 2024-01-30 14:11:54 +08:00
Lightczx
9a8827fb40 fix typo 2024-01-30 11:54:09 +08:00
DismissedLight
d88a6ca301 Merge pull request #1327 from DGP-Studio/l10n_develop 2024-01-30 11:29:56 +08:00
DismissedLight
5d401794e5 Merge branch 'develop' into l10n_develop 2024-01-30 11:29:48 +08:00
Lightczx
26396443dc add 4.4 tower wave description 2024-01-30 11:18:37 +08:00
Lightczx
6b755d934d setting width trigger performance 2024-01-30 09:26:43 +08:00
DismissedLight
7d5b057269 Merge pull request #1337 from Scighost/develop 2024-01-30 09:21:17 +08:00
Lightczx
917c173eb2 code style 2024-01-30 09:18:11 +08:00
Scighost
28d702422e Responsive setting page 2024-01-29 20:21:30 +08:00
Lightczx
7612ab5da3 fix #1331 2024-01-29 16:37:49 +08:00
Lightczx
ab436ecb2f add package convert directory permission override 2024-01-29 16:26:50 +08:00
Lightczx
457e3ff4d5 fix #1333 2024-01-29 16:05:53 +08:00
Lightczx
224c4e52ea Activation using NamedPipe 2024-01-29 15:51:09 +08:00
DismissedLight
2a5c7b21fd Merge pull request #1335 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-5aeabc7cca 2024-01-29 15:50:29 +08:00
dependabot[bot]
53f8291a66 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.1.1 to 3.2.0
- [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.1.1...v.3.2.0)

Updates `MSTest.TestFramework` from 3.1.1 to 3.2.0
- [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.1.1...v.3.2.0)

---
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-01-29 07:25:34 +00:00
DismissedLight
b621d5406a ui update 2024-01-28 21:13:50 +08:00
Masterain
12f4847aea New translations sh.resx (Portuguese) 2024-01-28 02:29:48 -08:00
Masterain
39a3d31f38 New translations sh.resx (Indonesian) 2024-01-28 02:29:47 -08:00
Masterain
70ac0b13a5 New translations sh.resx (English) 2024-01-28 02:29:46 -08:00
Masterain
a3520a4991 New translations sh.resx (Chinese Traditional) 2024-01-28 02:29:45 -08:00
Masterain
aed0284e4b New translations sh.resx (Russian) 2024-01-28 02:29:44 -08:00
Masterain
96ef07cbe5 New translations sh.resx (Korean) 2024-01-28 02:29:43 -08:00
Masterain
d1f37f37ac New translations sh.resx (Japanese) 2024-01-28 02:29:42 -08:00
DismissedLight
1fe09f3069 impl #1279 2024-01-28 17:44:15 +08:00
DismissedLight
32d9355c3a refine http related code 2024-01-27 23:11:41 +08:00
Masterain
71f170d51e New translations sh.resx (Portuguese) 2024-01-27 01:57:10 -08:00
Masterain
60015b6354 New translations sh.resx (Indonesian) 2024-01-27 01:57:08 -08:00
Masterain
eecae3ea4f New translations sh.resx (English) 2024-01-27 01:57:07 -08:00
Masterain
1831166f1e New translations sh.resx (Chinese Traditional) 2024-01-27 01:57:06 -08:00
Masterain
a98915ea24 New translations sh.resx (Russian) 2024-01-27 01:57:05 -08:00
Masterain
0d46656f57 New translations sh.resx (Korean) 2024-01-27 01:57:04 -08:00
Masterain
c814a5c28f New translations sh.resx (Japanese) 2024-01-27 01:57:03 -08:00
DismissedLight
9c3d59cc6f fix loopback 2024-01-27 13:52:48 +08:00
DismissedLight
890cf3f3ea Merge pull request #1286 from DGP-Studio/feat/feedback_network 2024-01-27 13:02:28 +08:00
DismissedLight
196bbb54c3 code style 2024-01-27 13:01:56 +08:00
DismissedLight
0481b9e474 pt resx as analyzer files 2024-01-27 09:56:35 +08:00
qhy040404
c4f3eb68e8 code style 2024-01-27 09:19:18 +08:00
qhy040404
c2e9f3a926 verify set 2024-01-27 00:47:09 +08:00
qhy040404
fb1fe3e40f add loopback status 2024-01-27 00:38:41 +08:00
qhy040404
75ed512e4a add current proxy to feedback page 2024-01-26 22:08:38 +08:00
Masterain
dd4dd33d93 New translations sh.resx (Japanese) 2024-01-26 01:47:29 -08:00
Lightczx
1e216e9823 refine com import 2024-01-26 17:18:57 +08:00
Lightczx
f823cb5f1a ui rework 2024-01-26 15:38:00 +08:00
Lightczx
2c6d25f0a3 limit update thread count 2024-01-25 17:22:32 +08:00
Masterain
817f768263 New translations sh.resx (Portuguese) 2024-01-25 00:36:14 -08:00
Masterain
2998fbb167 New translations sh.resx (English) 2024-01-25 00:36:12 -08:00
Masterain
8f0f94054d New translations sh.resx (Russian) 2024-01-25 00:36:10 -08:00
Lightczx
17a5d4d3a2 impl #1320 2024-01-25 15:59:52 +08:00
Lightczx
a1c604e68a add required win32 apis 2024-01-25 15:50:46 +08:00
Lightczx
948ec9a822 code style 2024-01-25 12:55:26 +08:00
Lightczx
f83174d690 com interface impl IUnknown 2024-01-25 11:24:17 +08:00
Lightczx
d686debbfb manually pinvoke from win32metadata 2024-01-25 11:01:45 +08:00
Lightczx
45248d75e1 pt-BR -> pt 2024-01-19 16:52:04 +08:00
Lightczx
22646cfab2 windows app sdk self contained 2024-01-19 16:25:57 +08:00
Lightczx
d0237a3c89 Add pt-BR locale 2024-01-19 15:41:11 +08:00
Tony
73c80fad10 Finish PT-BR translation (#1314) 2024-01-19 08:17:53 +08:00
Masterain
320bed9fcb New Crowdin updates (#1312)
* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Portuguese)
2024-01-17 15:10:26 -08:00
qhy040404
bb12aca3b4 Add PT translation (unfinished) (#1311)
Co-authored-by: t0piy <t.tony.br01@gmail.com>
Co-authored-by: Tony <66571593+t0piy@users.noreply.github.com>
2024-01-17 15:06:53 -08:00
DismissedLight
c7b5d98fb1 Merge pull request #1295 from DGP-Studio/fix/daily_note_task 2024-01-15 14:24:31 +08:00
qhy040404
7cc96f94f2 code style 2024-01-12 09:30:11 +08:00
qhy040404
b35355f9a3 improve daily note information 2024-01-11 20:39:02 +08:00
Masterain
745815657d New Crowdin updates (#1293) 2024-01-11 19:19:43 +08:00
DismissedLight
d93a9f41f3 bump version 2024-01-11 19:07:12 +08:00
qhy040404
910f099c6d reset debug console state (#1290)
Co-authored-by: DismissedLight <1686188646@qq.com>
2024-01-11 19:04:23 +08:00
Lightczx
e68449ec0c Update NativeMethods.txt 2024-01-10 17:06:23 +08:00
Lightczx
e484fbed21 update dependency 2024-01-10 10:42:35 +08:00
Masterain
88af6d28a9 New Crowdin updates (#1282)
* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)
2024-01-09 00:23:28 -08:00
Lightczx
3ab34f0992 Update HtmlDescriptionTextBlock.cs 2024-01-09 10:53:07 +08:00
Lightczx
5e875a7f18 add strings to resources 2 2024-01-09 10:38:33 +08:00
Lightczx
89d98748e8 add strings to resources 2024-01-09 10:10:31 +08:00
Masterain
d33cd894b9 New Crowdin updates (#1256)
* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)

* New translations sh.resx (Japanese)

* New translations sh.resx (Korean)

* New translations sh.resx (Russian)

* New translations sh.resx (Chinese Traditional)

* New translations sh.resx (English)

* New translations sh.resx (Indonesian)
2024-01-08 14:26:30 -08:00
DismissedLight
f0c19b419e Merge pull request #1276 from DGP-Studio/feat/dynamic_proxy 2024-01-08 22:20:30 +08:00
DismissedLight
f1d9787e45 fix method call 2024-01-08 22:18:07 +08:00
DismissedLight
5f9b4a7cb2 refactor RegistryWatcher 2024-01-08 21:55:59 +08:00
qhy040404
8710150897 use reflect to reduce code size 2024-01-08 20:19:05 +08:00
qhy040404
92c1b12764 dynamic proxy 2024-01-08 18:24:02 +08:00
Lightczx
d73bd557f3 remove settings appearance backdrop transparent 2024-01-08 16:52:37 +08:00
Lightczx
777d7d1056 remove winrt marshaller 2024-01-08 16:30:53 +08:00
Lightczx
1a944dae9c add transparent backdrop 2024-01-08 15:16:41 +08:00
Lightczx
a26c52ba97 typo 2024-01-08 11:43:45 +08:00
Lightczx
5fab03d57e Update FeedbackPage.xaml 2024-01-08 11:41:23 +08:00
Lightczx
e8a459cb41 refine #1039 2024-01-08 11:36:16 +08:00
DismissedLight
04df5a7bf1 impl #1039 2024-01-07 23:23:59 +08:00
DismissedLight
1ebcc2fc89 add documentation client 2024-01-07 14:49:02 +08:00
DismissedLight
e9917e788d Merge pull request #1273 from DGP-Studio/feat/identify_monitor 2024-01-06 23:39:41 +08:00
DismissedLight
9665876d52 code style 2 2024-01-06 23:38:26 +08:00
DismissedLight
2698761594 fix convert game path 2024-01-06 20:03:14 +08:00
302 changed files with 15461 additions and 2789 deletions

View File

@@ -51,7 +51,7 @@ body:
description: |
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
如果你的程序已经无法启动,请下载并运行[工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
validations:
required: false
@@ -87,7 +87,7 @@ body:
label: 发生了什么?
description: |
详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
如果你无法找到该日志,请下载并运行[工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将转储问题日志至工具运行目录中的 `Snap.Hutao Error Log.txt`
如果你无法找到该日志,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将转储问题日志至工具运行目录中的 `Snap.Hutao Error Log.txt`
validations:
required: true

View File

@@ -51,7 +51,7 @@ body:
description: |
In Snap Hutao's settings page, you can find and copy your device ID
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
If your program cannot startup, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
If your program cannot startup, please download and run [Diagnosis Tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
validations:
required: false
@@ -87,7 +87,7 @@ body:
label: What Happened?
description: |
Describe your issue in detail to help us identify the issue. **If your issue is about program crash, you should check Windows Event Viewer, and attach associated `.Net Error` details here**If your program cannot startup, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1), it will shows your device ID.
If you cannot find it, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will dump the error log to `Snap.Hutao Error Log.txt` in the working directory of the tool.
If you cannot find it, please download and run [Diagnosis Tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will dump the error log to `Snap.Hutao Error Log.txt` in the working directory of the tool.
validations:
required: true

View File

@@ -61,6 +61,9 @@ release:
- name: "$THIS_SHA256SUMS_NAME"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_SHA256SUMS_NAME?inline=false"
link_type: other
- name: "artifact_archive"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/download?file_type=archive"
link_type: other
Refresh:
stage: refresh

View File

@@ -44,9 +44,7 @@ Install with Snap Hutao MSIX package, can be installed with Windows built-in App
### 特定的原神项目 / Specific Genshin-related Projects
* [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)
* [Scighost/Starward](https://github.com/Scighost/Starward)
### 使用的技术栈 / Tech Stack
@@ -57,7 +55,6 @@ Install with Snap Hutao MSIX package, can be installed with Windows built-in App
* [dotnet/efcore](https://github.com/dotnet/efcore)
* [dotnet/runtime](https://github.com/dotnet/runtime)
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)
* [microsoft/CsWin32](https://github.com/microsoft/CsWin32)
* [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)

View File

@@ -160,9 +160,39 @@ public sealed class GeniusInvokationDecoding
ushort[] testKnownResult =
[
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
060,
019,
001,
079,
120,
120,
129,
151,
151,
153,
153,
181,
184,
184,
185,
185,
194,
194,
200,
200,
201,
201,
217,
217,
219,
241,
241,
244,
244,
245,
245,
270,
270,
];
CollectionAssert.AreEqual(resultArray, testKnownResult);

View File

@@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -1,11 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
"allowMarshaling": true,
"useSafeHandles": false,
"comInterop": {
"preserveSigMethods": [
"IFileOpenDialog.Show",
"IFileSaveDialog.Show"
]
}
}

View File

@@ -1,94 +0,0 @@
// COMCTL32
DefSubclassProc
RemoveWindowSubclass
SetWindowSubclass
// DWMAPI
DwmSetWindowAttribute
// GDI32
GetDeviceCaps
// KERNEL32
AllocConsole
CloseHandle
CreateEventW
CreateRemoteThread
FreeConsole
GetConsoleMode
GetModuleHandleW
GetProcAddress
GetStdHandle
K32EnumProcessModules
K32GetModuleBaseNameW
K32GetModuleInformation
ReadProcessMemory
SetConsoleMode
SetConsoleTitle
SetEvent
VirtualAlloc
VirtualAllocEx
VirtualFree
VirtualFreeEx
WaitForSingleObject
WriteProcessMemory
// OLE32
CoCreateInstance
CoWaitForMultipleObjects
// SHELL32
SHCreateItemFromParsingName
// USER32
AttachThreadInput
FindWindowExW
GetCursorPos
GetDC
GetDpiForWindow
GetForegroundWindow
GetWindowPlacement
GetWindowThreadProcessId
ReleaseDC
RegisterHotKey
SendInput
SetForegroundWindow
UnregisterHotKey
// COM
FileOpenDialog
FileSaveDialog
IFileOpenDialog
IFileSaveDialog
IPersistFile
IShellLinkDataList
IShellLinkW
ShellLink
SHELL_LINK_DATA_FLAGS
// WinRT
IMemoryBufferByteAccess
// Const value
E_FAIL
INFINITE
RPC_E_WRONG_THREAD
MAX_PATH
WM_GETMINMAXINFO
WM_HOTKEY
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP
WM_NULL
// Type & Enum definition
HRESULT_FROM_WIN32
SLGP_FLAGS
// System.Threading
LPTHREAD_START_ROUTINE
// UI.WindowsAndMessaging
MINMAXINFO
// System.Com
CWMO_FLAGS

View File

@@ -1,17 +0,0 @@
using System;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
namespace Windows.Win32;
internal static partial class PInvoke
{
/// <inheritdoc cref="CoCreateInstance(Guid*, object, CLSCTX, Guid*, out object)"/>
internal static unsafe HRESULT CoCreateInstance<TClass, TInterface>(object? pUnkOuter, CLSCTX dwClsContext, out TInterface ppv)
where TInterface : class
{
HRESULT hr = CoCreateInstance(typeof(TClass).GUID, pUnkOuter, dwClsContext, typeof(TInterface).GUID, out object o);
ppv = (TInterface)o;
return hr;
}
}

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="NativeMethods.json" />
<None Remove="NativeMethods.txt" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="NativeMethods.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1,24 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
using Windows.Win32.UI.WindowsAndMessaging;
[assembly: InternalsVisibleTo("Snap.Hutao")]
namespace Snap.Hutao.Win32;
/// <summary>
/// 结构体封送
/// </summary>
internal static class StructMarshal
{
/// <summary>
/// 构造一个新的 <see cref="Windows.Win32.UI.WindowsAndMessaging.WINDOWPLACEMENT"/>
/// </summary>
/// <returns>新的实例</returns>
public static unsafe WINDOWPLACEMENT WINDOWPLACEMENT()
{
return new() { length = unchecked((uint)sizeof(WINDOWPLACEMENT)) };
}
}

View File

@@ -1,72 +0,0 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Windows.Win32.CsWin32.InteropServices;
internal class WinRTCustomMarshaler : ICustomMarshaler
{
private static readonly string? AssemblyFullName = typeof(Windows.Foundation.IMemoryBuffer).Assembly.FullName;
private readonly string className;
private bool lookedForFromAbi;
private MethodInfo? fromAbiMethod;
private WinRTCustomMarshaler(string className)
{
this.className = className;
}
public static ICustomMarshaler GetInstance(string cookie)
{
return new WinRTCustomMarshaler(cookie);
}
public void CleanUpManagedData(object ManagedObj)
{
}
public void CleanUpNativeData(nint pNativeData)
{
Marshal.Release(pNativeData);
}
public int GetNativeDataSize()
{
throw new NotSupportedException();
}
public nint MarshalManagedToNative(object ManagedObj)
{
throw new NotSupportedException();
}
public object MarshalNativeToManaged(nint thisPtr)
{
return className switch
{
"Windows.System.DispatcherQueueController" => Windows.System.DispatcherQueueController.FromAbi(thisPtr),
_ => MarshalNativeToManagedSlow(thisPtr),
};
}
private object MarshalNativeToManagedSlow(nint pNativeData)
{
if (!lookedForFromAbi)
{
Type? type = Type.GetType($"{className}, {AssemblyFullName}");
fromAbiMethod = type?.GetMethod("FromAbi");
lookedForFromAbi = true;
}
if (fromAbiMethod is not null)
{
return fromAbiMethod.Invoke(default, new object[] { pNativeData })!;
}
else
{
return Marshal.GetObjectForIUnknown(pNativeData);
}
}
}

View File

@@ -13,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{0F7ABEB2-5107-4037-B9DC-84D288FB0801}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -67,32 +65,16 @@ Global
{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
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.ActiveCfg = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.Build.0 = Debug|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.Build.0 = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.ActiveCfg = Release|Any CPU
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]}
RESX_ShowErrorsInErrorList = False
RESX_SortFileContentOnSave = True
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
RESX_NeutralResourcesLanguage = zh-CN
RESX_AutoApplyExistingTranslations = False
RESX_NeutralResourcesLanguage = zh-CN
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
RESX_SortFileContentOnSave = True
RESX_ShowErrorsInErrorList = False
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]}
EndGlobalSection
EndGlobal

View File

@@ -32,6 +32,7 @@
x:Key="LargeGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,0,12,12"/>
</Style>
<Style

View File

@@ -6,6 +6,7 @@ using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.LifeCycle.InterProcess;
using Snap.Hutao.Core.Shell;
using System.Diagnostics;
@@ -36,8 +37,6 @@ public sealed partial class App : Application
----------------------------------------------------------------
""";
private const string AppInstanceKey = "main";
private readonly IServiceProvider serviceProvider;
private readonly IActivation activation;
private readonly ILogger<App> logger;
@@ -50,7 +49,6 @@ public sealed partial class App : Application
{
// Load app resource
InitializeComponent();
activation = serviceProvider.GetRequiredService<IActivation>();
logger = serviceProvider.GetRequiredService<ILogger<App>>();
serviceProvider.GetRequiredService<ExceptionRecorder>().Record(this);
@@ -64,25 +62,21 @@ public sealed partial class App : Application
try
{
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance firstInstance = AppInstance.FindOrRegisterForKey(AppInstanceKey);
if (firstInstance.IsCurrent)
if (serviceProvider.GetRequiredService<PrivateNamedPipeClient>().TryRedirectActivationTo(activatedEventArgs))
{
logger.LogInformation(ConsoleBanner);
LogDiagnosticInformation();
// manually invoke
activation.NonRedirectToActivate(firstInstance, activatedEventArgs);
activation.InitializeWith(firstInstance);
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
}
else
{
// Redirect the activation (and args) to the "main" instance, and exit.
firstInstance.RedirectActivationTo(activatedEventArgs);
Process.GetCurrentProcess().Kill();
Exit();
return;
}
logger.LogInformation(ConsoleBanner);
LogDiagnosticInformation();
// manually invoke
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
activation.Initialize();
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
}
catch
{

View File

@@ -0,0 +1,39 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("PaneCornerRadius", typeof(CornerRadius), default, nameof(OnPaneCornerRadiusChanged), IsAttached = true, AttachedType = typeof(NavigationView))]
public sealed partial class NavigationViewHelper
{
private static void OnPaneCornerRadiusChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
NavigationView navigationView = (NavigationView)dp;
CornerRadius newValue = (CornerRadius)args.NewValue;
if (navigationView.IsLoaded)
{
SetNavigationViewPaneCornerRadius(navigationView, newValue);
return;
}
navigationView.Loaded += (s, e) =>
{
NavigationView loadedNavigationView = (NavigationView)s;
SetNavigationViewPaneCornerRadius(loadedNavigationView, newValue);
};
}
private static void SetNavigationViewPaneCornerRadius(NavigationView navigationView, CornerRadius value)
{
if (navigationView.FindDescendant("RootSplitView") is SplitView splitView)
{
splitView.CornerRadius = value;
}
}
}

View File

@@ -7,7 +7,6 @@ using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("LeftPanelMaxWidth", typeof(double), IsAttached = true, AttachedType = typeof(ScrollViewer))]
[DependencyProperty("RightPanel", typeof(UIElement), IsAttached = true, AttachedType = typeof(ScrollViewer))]
public sealed partial class ScrollViewerHelper
{

View File

@@ -20,4 +20,4 @@ public sealed partial class SettingsExpanderHelper
}
}
}
}
}

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Control;
[TemplateVisualState(Name = "LoadingIn", GroupName = "CommonStates")]
[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
[TemplatePart(Name = "ContentGrid", Type = typeof(FrameworkElement))]
internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
{
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));

View File

@@ -3,7 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shc="using:Snap.Hutao.Control">
<Style TargetType="shc:Loading">
<Style BasedOn="{StaticResource DefaultLoadingStyle}" TargetType="shc:Loading"/>
<Style x:Key="DefaultLoadingStyle" TargetType="shc:Loading">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>

View File

@@ -18,12 +18,15 @@ internal sealed class FontIconExtension : MarkupExtension
/// </summary>
public string Glyph { get; set; } = default!;
public double FontSize { get; set; } = 12;
/// <inheritdoc/>
protected override object ProvideValue()
{
return new FontIcon()
{
Glyph = Glyph,
FontSize = FontSize,
};
}
}

View File

@@ -49,7 +49,8 @@ internal struct Rgba32
/// <param name="code">RGBA 代码</param>
public unsafe Rgba32(uint code)
{
// RRGGBBAA -> AABBGGRR
// uint layout: 0xRRGGBBAA -> AABBGGRR
// AABBGGRR -> RRGGBBAA
fixed (Rgba32* pSelf = &this)
{
*(uint*)pSelf = BinaryPrimitives.ReverseEndianness(code);

View File

@@ -1,10 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.System.WinRT;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Win32;
using Windows.Win32.System.WinRT;
using WinRT;
namespace Snap.Hutao.Control.Media;

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Controls;
namespace Snap.Hutao.Control.Panel;
[DependencyProperty("MinItemWidth", typeof(double))]
internal sealed partial class UniformPanel : UniformGrid
{
public UniformPanel()
{
Columns = 1;
SizeChanged += OnSizeChanged;
}
private void OnSizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
{
Columns = (int)((e.NewSize.Width + ColumnSpacing) / (MinItemWidth + ColumnSpacing));
}
}

View File

@@ -8,6 +8,7 @@ using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Metadata;
using Windows.Foundation;
using Windows.UI;
@@ -23,8 +24,11 @@ namespace Snap.Hutao.Control.Text;
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
internal sealed partial class DescriptionTextBlock : ContentControl
{
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".Length;
private static readonly int RgbaColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int RgbaColorTagLeftLength = "<color=#FFFFFFFF>".Length;
private static readonly int RgbColorTagFullLength = "<color=#FFFFFF></color>".Length;
private static readonly int RgbColorTagLeftLength = "<color=#FFFFFF>".Length;
private static readonly int ItalicTagFullLength = "<i></i>".Length;
private static readonly int ItalicTagLeftLength = "<i>".Length;
@@ -51,9 +55,16 @@ internal sealed partial class DescriptionTextBlock : ContentControl
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
ReadOnlySpan<char> description = (string)e.NewValue;
ReadOnlySpan<char> description = SpecialNameHandler.Handle((string)e.NewValue);
UpdateDescription(textBlock, description);
try
{
UpdateDescription(textBlock, description);
}
catch (Exception ex)
{
_ = ex;
}
}
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -81,13 +92,32 @@ internal sealed partial class DescriptionTextBlock : ContentControl
// color tag
else if (description[i..].StartsWith("<c"))
{
AppendText(textBlock, description[last..i]);
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
switch (description[i..].IndexOf('>'))
{
case 16: // RgbaColorTag
{
AppendText(textBlock, description[last..i]);
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
int length = description[(i + RgbaColorTagLeftLength)..].IndexOf('<');
AppendColorText(textBlock, description.Slice(i + RgbaColorTagLeftLength, length), color);
i += length + ColorTagFullLength;
last = i;
i += length + RgbaColorTagFullLength;
last = i;
break;
}
case 14: // RgbColorTag
{
AppendText(textBlock, description[last..i]);
Rgba32 color = new(description.Slice(i + 8, 6).ToString());
int length = description[(i + RgbColorTagLeftLength)..].IndexOf('<');
AppendColorText(textBlock, description.Slice(i + RgbColorTagLeftLength, length), color);
i += length + RgbColorTagFullLength;
last = i;
break;
}
}
}
// italic

View File

@@ -156,7 +156,7 @@ internal sealed partial class HtmlDescriptionTextBlock : ContentControl
text.Inlines.Add(new Run
{
Text = slice.ToString(),
FontWeight = FontWeights.Bold,
FontWeight = FontWeights.SemiBold,
});
}

View File

@@ -24,10 +24,43 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
</Style>
<Style
x:Key="AcrylicBaseHighBorderCardStyle"
BasedOn="{StaticResource BorderCardStyle}"
TargetType="Border">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="{ThemeResource SystemControlBaseHighAcrylicElementBrush}"/>
</Style>
<Style
x:Key="AcrylicBorderCardStyle"
BasedOn="{StaticResource BorderCardStyle}"
TargetType="Border">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"/>
</Style>
<Style x:Key="GridCardStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
</Style>
<Style
x:Key="AcrylicGridCardStyle"
BasedOn="{StaticResource GridCardStyle}"
TargetType="Grid">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"/>
</Style>
<Style
x:Key="AcrylicBaseHighGridCardStyle"
BasedOn="{StaticResource GridCardStyle}"
TargetType="Grid">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="{ThemeResource SystemControlBaseHighAcrylicElementBrush}"/>
</Style>
</ResourceDictionary>

View File

@@ -2,6 +2,53 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shch="using:Snap.Hutao.Control.Helper">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<AcrylicBrush
x:Key="InfoBarErrorSeverityBackgroundBrush"
FallbackColor="#FDE7E9"
TintColor="#FDE7E9"
TintOpacity="0.6"/>
<AcrylicBrush
x:Key="InfoBarWarningSeverityBackgroundBrush"
FallbackColor="#FFF4CE"
TintColor="#FFF4CE"
TintOpacity="0.6"/>
<AcrylicBrush
x:Key="InfoBarSuccessSeverityBackgroundBrush"
FallbackColor="#DFF6DD"
TintColor="#DFF6DD"
TintOpacity="0.6"/>
<AcrylicBrush
x:Key="InfoBarInformationalSeverityBackgroundBrush"
FallbackColor="#80F6F6F6"
TintColor="#80F6F6F6"
TintOpacity="0.6"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<AcrylicBrush
x:Key="InfoBarErrorSeverityBackgroundBrush"
FallbackColor="#442726"
TintColor="#442726"
TintOpacity="0.6"/>
<AcrylicBrush
x:Key="InfoBarWarningSeverityBackgroundBrush"
FallbackColor="#433519"
TintColor="#433519"
TintOpacity="0.6"/>
<AcrylicBrush
x:Key="InfoBarSuccessSeverityBackgroundBrush"
FallbackColor="#393D1B"
TintColor="#393D1B"
TintOpacity="0.6"/>
<AcrylicBrush
x:Key="InfoBarInformationalSeverityBackgroundBrush"
FallbackColor="#34424d"
TintColor="#34424d"
TintOpacity="0.6"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<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>

View File

@@ -20,6 +20,9 @@
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
<StackPanel Spacing="4"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="StackPanelSpacing8Template">
<StackPanel Spacing="8"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="UniformGridColumns2Spacing2Template">
<cwcont:UniformGrid
ColumnSpacing="2"

View File

@@ -9,6 +9,8 @@
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
<x:Double x:Key="LargeAppBarButtonWidth">100</x:Double>
<x:Double x:Key="AppBarThemeCompactActualHeight">50</x:Double>
<!-- ProgressBar -->
<x:Double x:Key="LargeBackgroundProgressBarOpacity">0.2</x:Double>
</ResourceDictionary>

View File

@@ -1,5 +1,385 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cw="using:CommunityToolkit.WinUI">
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
<Thickness x:Key="PivotItemMargin">0</Thickness>
<Style x:Key="CardPivotStyle" TargetType="Pivot">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="{ThemeResource PivotBackground}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Pivot">
<Grid
x:Name="RootElement"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}">
<Grid.Resources>
<Style x:Key="BaseContentControlStyle" TargetType="ContentControl">
<Setter Property="FontFamily" Value="XamlAutoFontFamily"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
OpticalMarginAlignment="TrimSideBearings"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="TitleContentControlStyle"
BasedOn="{StaticResource BaseContentControlStyle}"
TargetType="ContentControl">
<Setter Property="FontFamily" Value="{ThemeResource PivotTitleFontFamily}"/>
<Setter Property="FontWeight" Value="{ThemeResource PivotTitleThemeFontWeight}"/>
<Setter Property="FontSize" Value="{ThemeResource PivotTitleFontSize}"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl
x:Name="TitleContentControl"
Grid.Row="0"
Margin="{StaticResource PivotPortraitThemePadding}"
Content="{TemplateBinding Title}"
ContentTemplate="{TemplateBinding TitleTemplate}"
IsTabStop="False"
Style="{StaticResource TitleContentControlStyle}"
Visibility="Collapsed"/>
<Grid Grid.Row="1">
<Grid.Resources>
<ControlTemplate x:Key="NextTemplate" TargetType="Button">
<Border
x:Name="Root"
Background="{ThemeResource PivotNextButtonBackground}"
BorderBrush="{ThemeResource PivotNextButtonBorderBrush}"
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
<FontIcon
x:Name="Arrow"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Foreground="{ThemeResource PivotNextButtonForeground}"
Glyph="&#xE76C;"
MirroredWhenRightToLeft="True"
UseLayoutRounding="False"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonBorderBrushPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotNextButtonForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
<ControlTemplate x:Key="PreviousTemplate" TargetType="Button">
<Border
x:Name="Root"
Background="{ThemeResource PivotPreviousButtonBackground}"
BorderBrush="{ThemeResource PivotPreviousButtonBorderBrush}"
BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}">
<FontIcon
x:Name="Arrow"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Foreground="{ThemeResource PivotPreviousButtonForeground}"
Glyph="&#xE76B;"
MirroredWhenRightToLeft="True"
UseLayoutRounding="False"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPointerOver}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBackgroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonBorderBrushPressed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Arrow" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPreviousButtonForegroundPressed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Grid.Resources>
<ScrollViewer
x:Name="ScrollViewer"
Margin="{TemplateBinding Padding}"
VerticalContentAlignment="Stretch"
BringIntoViewOnFocusChange="False"
HorizontalScrollBarVisibility="Hidden"
HorizontalSnapPointsAlignment="Center"
HorizontalSnapPointsType="MandatorySingle"
Template="{StaticResource ScrollViewerScrollBarlessTemplate}"
VerticalScrollBarVisibility="Disabled"
VerticalScrollMode="Disabled"
VerticalSnapPointsType="None"
ZoomMode="Disabled">
<PivotPanel x:Name="Panel" VerticalAlignment="Stretch">
<Grid x:Name="PivotLayoutElement">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.RenderTransform>
<CompositeTransform x:Name="PivotLayoutElementTranslateTransform"/>
</Grid.RenderTransform>
<ItemsPresenter x:Name="PivotItemPresenter" Grid.Row="1">
<ItemsPresenter.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="ItemsPresenterTranslateTransform"/>
<CompositeTransform x:Name="ItemsPresenterCompositeTransform"/>
</TransformGroup>
</ItemsPresenter.RenderTransform>
</ItemsPresenter>
<Border
Grid.Row="0"
Margin="16,16,16,0"
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Grid Style="{ThemeResource AcrylicBaseHighGridCardStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter
x:Name="LeftHeaderPresenter"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{TemplateBinding LeftHeader}"
ContentTemplate="{TemplateBinding LeftHeaderTemplate}"/>
<ContentControl
x:Name="HeaderClipper"
Grid.Column="1"
HorizontalContentAlignment="Stretch"
UseSystemFocusVisuals="{StaticResource UseSystemFocusVisuals}">
<ContentControl.Clip>
<RectangleGeometry x:Name="HeaderClipperGeometry"/>
</ContentControl.Clip>
<Grid>
<Grid.RenderTransform>
<CompositeTransform x:Name="HeaderOffsetTranslateTransform"/>
</Grid.RenderTransform>
<PivotHeaderPanel x:Name="StaticHeader" Visibility="Collapsed">
<PivotHeaderPanel.RenderTransform>
<CompositeTransform x:Name="StaticHeaderTranslateTransform"/>
</PivotHeaderPanel.RenderTransform>
</PivotHeaderPanel>
<PivotHeaderPanel x:Name="Header">
<PivotHeaderPanel.RenderTransform>
<CompositeTransform x:Name="HeaderTranslateTransform"/>
</PivotHeaderPanel.RenderTransform>
</PivotHeaderPanel>
<Rectangle
x:Name="FocusFollower"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Control.IsTemplateFocusTarget="True"
Fill="Transparent"
IsHitTestVisible="False"/>
</Grid>
</ContentControl>
<Button
x:Name="PreviousButton"
Grid.Column="1"
Width="20"
Height="36"
Margin="{ThemeResource PivotNavButtonMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="Transparent"
IsEnabled="False"
IsTabStop="False"
Opacity="0"
Template="{StaticResource PreviousTemplate}"
UseSystemFocusVisuals="False"/>
<Button
x:Name="NextButton"
Grid.Column="1"
Width="20"
Height="36"
Margin="{ThemeResource PivotNavButtonMargin}"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="Transparent"
IsEnabled="False"
IsTabStop="False"
Opacity="0"
Template="{StaticResource NextTemplate}"
UseSystemFocusVisuals="False"/>
<ContentPresenter
x:Name="RightHeaderPresenter"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{TemplateBinding RightHeader}"
ContentTemplate="{TemplateBinding RightHeaderTemplate}"/>
</Grid>
</Border>
</Grid>
</PivotPanel>
</ScrollViewer>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Orientation">
<VisualState x:Name="Portrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPortraitThemePadding}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Landscape">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleContentControl" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotLandscapeThemePadding}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="NavigationButtonsVisibility">
<VisualState x:Name="NavigationButtonsHidden"/>
<VisualState x:Name="NavigationButtonsVisible">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PreviousButtonVisible">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PreviousButton" Storyboard.TargetProperty="IsEnabled">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="NextButtonVisible">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="Opacity">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NextButton" Storyboard.TargetProperty="IsEnabled">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="HeaderStates">
<VisualState x:Name="HeaderDynamic"/>
<VisualState x:Name="HeaderStatic">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Header" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="StaticHeader" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -42,7 +42,7 @@
Grid.ColumnSpan="2"
Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="{Binding Path=(shch:ScrollViewerHelper.LeftPanelMaxWidth), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollContentPresenter x:Name="ScrollContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/>

View File

@@ -1,4 +1,7 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls">
<!-- Settings -->
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
@@ -6,6 +9,12 @@
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
<x:Double x:Key="SettingsCardContentControlMinWidth">120</x:Double>
<x:Double x:Key="SettingsCardContentControlMinWidth2">160</x:Double>
<x:Double x:Key="SettingsCardContentControlSpacing">10</x:Double>
<Thickness x:Key="SettingsCardAlignSettingsExpanderPadding">16,16,44,16</Thickness>
<Thickness x:Key="SettingsExpanderItemHasIconPadding">16,8,16,8</Thickness>
<Style
x:Key="SettingsSectionHeaderTextBlockStyle"

View File

@@ -4,6 +4,7 @@
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
<x:String x:Key="DocumentLink_Loopback">https://hut.ao/zh/advanced/FAQ.html#%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86%E4%BD%BF%E7%94%A8%E8%83%A1%E6%A1%83%E5%B7%A5%E5%85%B7%E7%AE%B1</x:String>
<!-- Other -->
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
@@ -27,6 +28,7 @@
<!-- EmotionIcon -->
<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_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>

View File

@@ -2,9 +2,12 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Http;
using Snap.Hutao.Core.IO.Http.Proxy;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service;
using System.Globalization;
using System.Net.Http;
using System.Runtime.CompilerServices;
using Windows.Globalization;
@@ -31,7 +34,7 @@ internal static class DependencyInjection
.AddJsonOptions()
.AddDatabase()
.AddInjections()
.AddHttpClients()
.AddAllHttpClients()
// Discrete services
.AddSingleton<IMessenger, WeakReferenceMessenger>()
@@ -48,10 +51,10 @@ internal static class DependencyInjection
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InitializeCulture(this IServiceProvider serviceProvider)
{
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
CultureOptions cultureOptions = serviceProvider.GetRequiredService<CultureOptions>();
cultureOptions.SystemCulture = CultureInfo.CurrentCulture;
CultureInfo cultureInfo = appOptions.CurrentCulture;
CultureInfo cultureInfo = cultureOptions.CurrentCulture;
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;

View File

@@ -38,8 +38,8 @@ internal static class IocConfiguration
private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder)
{
RuntimeOptions hutaoOptions = provider.GetRequiredService<RuntimeOptions>();
string dbFile = System.IO.Path.Combine(hutaoOptions.DataFolder, "Userdata.db");
RuntimeOptions runtimeOptions = provider.GetRequiredService<RuntimeOptions>();
string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db");
string sqlConnectionString = $"Data Source={dbFile}";
// Temporarily create a context

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.IO.Http.Proxy;
using Snap.Hutao.Web.Hoyolab;
using System.Net.Http;
@@ -14,12 +15,26 @@ internal static partial class IocHttpClientConfiguration
{
private const string ApplicationJson = "application/json";
/// <summary>
/// 添加 <see cref="HttpClient"/>
/// 此方法将会自动生成
/// </summary>
/// <param name="services">集合</param>
/// <returns>可继续操作的集合</returns>
public static IServiceCollection AddAllHttpClients(this IServiceCollection services)
{
services
.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler())
.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
HttpClientHandler clientHandler = (HttpClientHandler)handler;
clientHandler.UseProxy = true;
clientHandler.Proxy = provider.GetRequiredService<DynamicHttpProxy>();
});
})
.AddHttpClients();
return services;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
/// <summary>
@@ -29,10 +44,10 @@ internal static partial class IocHttpClientConfiguration
/// <param name="client">配置后的客户端</param>
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
{
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
client.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
}
/// <summary>
@@ -89,6 +104,7 @@ internal static partial class IocHttpClientConfiguration
/// HoYoLAB web
/// </summary>
/// <param name="client">配置后的客户端</param>
[SuppressMessage("", "IDE0051")]
private static void XRpc4Configuration(HttpClient client)
{
client.Timeout = Timeout.InfiniteTimeSpan;

View File

@@ -11,10 +11,18 @@ internal sealed class HutaoException : Exception
Kind = kind;
}
public HutaoException(string message, Exception? innerException)
private HutaoException(string message, Exception? innerException)
: base($"{message}\n{innerException?.Message}", innerException)
{
}
public HutaoExceptionKind Kind { get; private set; }
public static void ThrowIf(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
{
if (condition)
{
throw new HutaoException(kind, message, innerException);
}
}
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Package;
using System.IO;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.ExceptionService;
@@ -35,6 +36,22 @@ internal static class ThrowHelper
throw new GameFileOperationException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static InvalidDataException InvalidData(string message, Exception? inner = default)
{
throw new InvalidDataException(message, inner);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void InvalidDataIf([DoesNotReturnIf(true)] bool condition, string message, Exception? inner = default)
{
if (condition)
{
throw new InvalidDataException(message, inner);
}
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static InvalidOperationException InvalidOperation(string message, Exception? inner = default)

View File

@@ -0,0 +1,83 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.NetworkManagement.WindowsFirewall;
using Snap.Hutao.Win32.Security;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.AdvApi32;
using static Snap.Hutao.Win32.ApiMsWinNetIsolation;
using static Snap.Hutao.Win32.Macros;
namespace Snap.Hutao.Core.IO.Http.Loopback;
[Injection(InjectAs.Singleton)]
internal sealed unsafe class LoopbackManager : ObservableObject
{
private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
private readonly string hutaoContainerStringSID = default!;
private bool isLoopbackEnabled;
public LoopbackManager(IServiceProvider serviceProvider)
{
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
INET_FIREWALL_APP_CONTAINER* pContainers = default;
try
{
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
for (uint i = 0; i < acCount; i++)
{
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
{
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
break;
}
}
}
finally
{
// This function returns 1 rather than 0 specfied in the document.
_ = NetworkIsolationFreeAppContainers(pContainers);
}
WIN32_ERROR error2 = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error2));
for (uint i = 0; i < accCount; i++)
{
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
{
IsLoopbackEnabled = true;
break;
}
}
}
public bool IsLoopbackEnabled { get => isLoopbackEnabled; private set => SetProperty(ref isLoopbackEnabled, value); }
public void EnableLoopback()
{
NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
List<SID_AND_ATTRIBUTES> sids = new((int)(accCount + 1));
for (uint i = 0; i < accCount; i++)
{
sids.Add(*(pSids + i));
}
ConvertStringSidToSidW(hutaoContainerStringSID, out PSID pSid);
SID_AND_ATTRIBUTES sidAndAttributes = default;
sidAndAttributes.Sid = pSid;
sids.Add(sidAndAttributes);
IsLoopbackEnabled = NetworkIsolationSetAppContainerConfig(CollectionsMarshal.AsSpan(sids)) is WIN32_ERROR.ERROR_SUCCESS;
}
}

View File

@@ -0,0 +1,110 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Win32.Registry;
using System.Net;
using System.Reflection;
namespace Snap.Hutao.Core.IO.Http.Proxy;
[Injection(InjectAs.Singleton)]
internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, IDisposable
{
private const string ProxySettingPath = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections";
private static readonly Lazy<MethodInfo> LazyConstructSystemProxyMethod = new(GetConstructSystemProxyMethod);
private readonly IServiceProvider serviceProvider;
private readonly RegistryWatcher watcher;
private IWebProxy innerProxy = default!;
public DynamicHttpProxy(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
UpdateInnerProxy();
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
watcher.Start();
}
public string CurrentProxyUri
{
get
{
Uri? proxyUri = GetProxy("https://hut.ao".ToUri());
return proxyUri is null
? SH.ViewPageFeedbackCurrentProxyNoProxyDescription
: proxyUri.AbsoluteUri;
}
}
public IWebProxy InnerProxy
{
get => innerProxy;
[MemberNotNull(nameof(innerProxy))]
set
{
if (ReferenceEquals(innerProxy, value))
{
return;
}
(innerProxy as IDisposable)?.Dispose();
innerProxy = value;
}
}
public ICredentials? Credentials
{
get => InnerProxy.Credentials;
set => InnerProxy.Credentials = value;
}
public Uri? GetProxy(Uri destination)
{
return InnerProxy.GetProxy(destination);
}
public bool IsBypassed(Uri host)
{
return InnerProxy.IsBypassed(host);
}
public void Dispose()
{
(innerProxy as IDisposable)?.Dispose();
watcher.Dispose();
}
public void OnSystemProxySettingsChanged()
{
UpdateInnerProxy();
// TaskContext can't be injected directly since there are some recursive dependencies.
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
taskContext.BeginInvokeOnMainThread(() => OnPropertyChanged(nameof(CurrentProxyUri)));
}
private static MethodInfo GetConstructSystemProxyMethod()
{
Type? systemProxyInfoType = typeof(System.Net.Http.SocketsHttpHandler).Assembly.GetType("System.Net.Http.SystemProxyInfo");
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
return constructSystemProxyMethod;
}
[MemberNotNull(nameof(innerProxy))]
private void UpdateInnerProxy()
{
IWebProxy? proxy = LazyConstructSystemProxyMethod.Value.Invoke(default, default) as IWebProxy;
ArgumentNullException.ThrowIfNull(proxy);
InnerProxy = proxy;
}
}

View File

@@ -18,6 +18,7 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
private readonly long contentLength;
private readonly int bufferSize;
private readonly SafeFileHandle destFileHandle;
private readonly int maxDegreeOfParallelism;
private readonly List<Shard> shards;
private HttpShardCopyWorker(HttpShardCopyWorkerOptions<TStatus> options)
@@ -28,6 +29,7 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
contentLength = options.ContentLength;
bufferSize = options.BufferSize;
destFileHandle = options.GetFileHandle();
maxDegreeOfParallelism = options.MaxDegreeOfParallelism;
shards = CalculateShards(contentLength);
static List<Shard> CalculateShards(long contentLength)
@@ -56,7 +58,11 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
public Task CopyAsync(IProgress<TStatus> progress, CancellationToken token = default)
{
ShardProgress shardProgress = new(progress, statusFactory, contentLength);
return Parallel.ForEachAsync(shards, token, (shard, token) => CopyShardAsync(shard, shardProgress, token));
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = maxDegreeOfParallelism,
};
return Parallel.ForEachAsync(shards, parallelOptions, (shard, token) => CopyShardAsync(shard, shardProgress, token));
async ValueTask CopyShardAsync(Shard shard, IProgress<ShardStatus> progress, CancellationToken token)
{

View File

@@ -22,6 +22,8 @@ internal sealed class HttpShardCopyWorkerOptions<TStatus>
public int BufferSize { get; set; } = 80 * 1024;
public int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount;
public SafeFileHandle GetFileHandle()
{
return File.OpenHandle(DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.RandomAccess | FileOptions.Asynchronous, ContentLength);

View File

@@ -3,7 +3,7 @@
using CommunityToolkit.WinUI.Notifications;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.LifeCycle.InterProcess;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Discord;
@@ -24,24 +24,9 @@ namespace Snap.Hutao.Core.LifeCycle;
[SuppressMessage("", "CA1001")]
internal sealed partial class Activation : IActivation
{
/// <summary>
/// 操作
/// </summary>
public const string Action = nameof(Action);
/// <summary>
/// Uid
/// </summary>
public const string Uid = nameof(Uid);
/// <summary>
/// 启动游戏启动参数
/// </summary>
public const string LaunchGame = nameof(LaunchGame);
/// <summary>
/// 从剪贴板导入成就
/// </summary>
public const string ImportUIAFFromClipboard = nameof(ImportUIAFFromClipboard);
private const string CategoryAchievement = "ACHIEVEMENT";
@@ -55,29 +40,20 @@ internal sealed partial class Activation : IActivation
private readonly SemaphoreSlim activateSemaphore = new(1);
/// <inheritdoc/>
public void Activate(object? sender, AppActivationArguments args)
public void Activate(HutaoActivationArguments args)
{
_ = sender;
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
{
HandleActivationAsync(args, true).SafeForget();
return;
}
HandleActivationAsync(args).SafeForget();
}
/// <inheritdoc/>
public void NonRedirectToActivate(object? sender, AppActivationArguments args)
public void Initialize()
{
_ = sender;
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
{
HandleActivationAsync(args, false).SafeForget();
}
}
/// <inheritdoc/>
public void InitializeWith(AppInstance appInstance)
{
appInstance.Activated += Activate;
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
ToastNotificationManagerCompat.OnActivated += NotificationActivate;
}
@@ -95,44 +71,40 @@ internal sealed partial class Activation : IActivation
}
}
private async ValueTask HandleActivationAsync(AppActivationArguments args, bool isRedirected)
private async ValueTask HandleActivationAsync(HutaoActivationArguments args)
{
if (activateSemaphore.CurrentCount > 0)
{
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
{
await HandleActivationCoreAsync(args, isRedirected).ConfigureAwait(false);
await HandleActivationCoreAsync(args).ConfigureAwait(false);
}
}
}
private async ValueTask HandleActivationCoreAsync(AppActivationArguments args, bool isRedirected)
private async ValueTask HandleActivationCoreAsync(HutaoActivationArguments args)
{
if (args.Kind == ExtendedActivationKind.Protocol)
if (args.Kind is HutaoActivationKind.Protocol)
{
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
await HandleUrlActivationAsync(uri, isRedirected).ConfigureAwait(false);
}
ArgumentNullException.ThrowIfNull(args.ProtocolActivatedUri);
await HandleUrlActivationAsync(args.ProtocolActivatedUri, args.IsRedirectTo).ConfigureAwait(false);
}
else if (args.Kind == ExtendedActivationKind.Launch)
else if (args.Kind is HutaoActivationKind.Launch)
{
if (args.TryGetLaunchActivatedArgument(out string? arguments))
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
switch (args.LaunchActivatedArguments)
{
switch (arguments)
{
case LaunchGame:
{
await HandleLaunchGameActionAsync().ConfigureAwait(false);
break;
}
case LaunchGame:
{
await HandleLaunchGameActionAsync().ConfigureAwait(false);
break;
}
default:
{
await HandleNormalLaunchActionAsync().ConfigureAwait(false);
break;
}
}
default:
{
await HandleNormalLaunchActionAsync().ConfigureAwait(false);
break;
}
}
}
}
@@ -194,7 +166,7 @@ internal sealed partial class Activation : IActivation
.SafeForget();
}
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirected)
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirectTo)
{
UriBuilder builder = new(uri);
@@ -207,13 +179,13 @@ internal sealed partial class Activation : IActivation
case CategoryAchievement:
{
await WaitMainWindowAsync().ConfigureAwait(false);
await HandleAchievementActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
await HandleAchievementActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
break;
}
case CategoryDailyNote:
{
await HandleDailyNoteActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
await HandleDailyNoteActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
break;
}
@@ -225,10 +197,10 @@ internal sealed partial class Activation : IActivation
}
}
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirected)
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirectTo)
{
_ = parameter;
_ = isRedirected;
_ = isRedirectTo;
switch (action)
{
case UrlActionImport:
@@ -245,7 +217,7 @@ internal sealed partial class Activation : IActivation
}
}
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirected)
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirectTo)
{
_ = parameter;
switch (action)
@@ -264,7 +236,7 @@ internal sealed partial class Activation : IActivation
}
// Check if it's redirected.
if (!isRedirected)
if (!isRedirectTo)
{
// It's a direct open process, should exit immediately.
Process.GetCurrentProcess().Kill();

View File

@@ -36,7 +36,7 @@ internal static class AppActivationArgumentsExtensions
/// <param name="activatedEventArgs">应用程序激活参数</param>
/// <param name="arguments">参数</param>
/// <returns>是否存在参数</returns>
public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
public static bool TryGetLaunchActivatedArguments(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
{
arguments = null;
if (activatedEventArgs.Data is not ILaunchActivatedEventArgs launchArgs)

View File

@@ -2,10 +2,11 @@
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
using Windows.Win32.Foundation;
using Windows.Win32.Security;
using Windows.Win32.System.Com;
using static Windows.Win32.PInvoke;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.Kernel32;
using static Snap.Hutao.Win32.Ole32;
namespace Snap.Hutao.Core.LifeCycle;
@@ -18,7 +19,7 @@ internal static class AppInstanceExtension
private static readonly WaitCallback RunActionWaitCallback = RunAction;
// Hold the reference here to prevent memory corruption.
private static HANDLE redirectEventHandle = HANDLE.Null;
private static HANDLE redirectEventHandle;
/// <summary>
/// 同步非阻塞重定向
@@ -27,19 +28,24 @@ internal static class AppInstanceExtension
/// <param name="args">参数</param>
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
{
redirectEventHandle = CreateEvent(default(SECURITY_ATTRIBUTES*), true, false, null);
// use ThreadPool.UnsafeQueueUserWorkItem to cancel stacktrace
// like ExecutionContext.SuppressFlow
ThreadPool.UnsafeQueueUserWorkItem(RunActionWaitCallback, () =>
try
{
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
redirectEventHandle = CreateEventW(default, true, false, default);
ReadOnlySpan<HANDLE> handles = new(ref redirectEventHandle);
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
CloseHandle(redirectEventHandle);
// use ThreadPool.UnsafeQueueUserWorkItem to cancel stacktrace
// like ExecutionContext.SuppressFlow
ThreadPool.UnsafeQueueUserWorkItem(RunActionWaitCallback, () =>
{
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
CoWaitForMultipleObjects(CWMO_FLAGS.CWMO_DEFAULT, INFINITE, [redirectEventHandle], out uint _);
}
finally
{
CloseHandle(redirectEventHandle);
}
}
[SuppressMessage("", "SH007")]

View File

@@ -3,7 +3,7 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Windows.Win32.Foundation;
using Snap.Hutao.Win32.Foundation;
using WinRT.Interop;
namespace Snap.Hutao.Core.LifeCycle;
@@ -19,6 +19,6 @@ internal static class CurrentWindowReferenceExtension
{
return reference.Window is IWindowOptionsSource optionsSource
? optionsSource.WindowOptions.Hwnd
: (HWND)WindowNative.GetWindowHandle(reference.Window);
: WindowNative.GetWindowHandle(reference.Window);
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
namespace Snap.Hutao.Core.LifeCycle;
internal sealed class HutaoActivationArguments
{
public bool IsRedirectTo { get; set; }
public HutaoActivationKind Kind { get; set; }
public Uri? ProtocolActivatedUri { get; set; }
public string? LaunchActivatedArguments { get; set; }
public static HutaoActivationArguments FromAppActivationArguments(AppActivationArguments args, bool isRedirected = false)
{
HutaoActivationArguments result = new()
{
IsRedirectTo = isRedirected,
};
switch (args.Kind)
{
case ExtendedActivationKind.Launch:
{
result.Kind = HutaoActivationKind.Launch;
if (args.TryGetLaunchActivatedArguments(out string? arguments))
{
result.LaunchActivatedArguments = arguments;
}
break;
}
case ExtendedActivationKind.Protocol:
{
result.Kind = HutaoActivationKind.Protocol;
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
result.ProtocolActivatedUri = uri;
}
break;
}
}
return result;
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle;
internal enum HutaoActivationKind
{
None,
Launch,
Protocol,
}

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
namespace Snap.Hutao.Core.LifeCycle;
/// <summary>
@@ -10,24 +8,7 @@ namespace Snap.Hutao.Core.LifeCycle;
/// </summary>
internal interface IActivation
{
/// <summary>
/// 响应激活事件
/// 激活事件一般不会在UI线程上触发
/// </summary>
/// <param name="sender">发送方</param>
/// <param name="args">激活参数</param>
void Activate(object? sender, AppActivationArguments args);
void Activate(HutaoActivationArguments args);
/// <summary>
/// 使用当前 App 实例初始化激活
/// </summary>
/// <param name="appInstance">App 实例</param>
void InitializeWith(AppInstance appInstance);
/// <summary>
/// 无转发触发激活事件
/// </summary>
/// <param name="sender">发送方</param>
/// <param name="args">激活参数</param>
void NonRedirectToActivate(object? sender, AppActivationArguments args);
void Initialize();
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO.Pipes;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
internal static class NamedPipeClientStreamExtension
{
public static bool TryConnectOnce(this NamedPipeClientStream clientStream)
{
try
{
clientStream.Connect(TimeSpan.Zero);
return true;
}
catch (TimeoutException)
{
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
internal enum PipePacketCommand : byte
{
None = 0,
RedirectActivation = 10,
}

View File

@@ -0,0 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
internal enum PipePacketContentType : byte
{
None = 0,
Json = 1,
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct PipePacketHeader
{
public byte Version;
public PipePacketType Type;
public PipePacketCommand Command;
public PipePacketContentType ContentType;
public int ContentLength;
public ulong Checksum;
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
internal enum PipePacketType : byte
{
None = 0,
Request = 1,
Response = 2,
Termination = 3,
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
using System.IO.Hashing;
using System.IO.Pipes;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
[Injection(InjectAs.Singleton)]
internal sealed class PrivateNamedPipeClient : IDisposable
{
private readonly NamedPipeClientStream clientStream = new(".", "Snap.Hutao.PrivateNamedPipe", PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
public unsafe bool TryRedirectActivationTo(AppActivationArguments args)
{
if (clientStream.TryConnectOnce())
{
{
PipePacketHeader redirectActivationPacket = default;
redirectActivationPacket.Version = 1;
redirectActivationPacket.Type = PipePacketType.Request;
redirectActivationPacket.Command = PipePacketCommand.RedirectActivation;
redirectActivationPacket.ContentType = PipePacketContentType.Json;
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(args, isRedirected: true);
byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(hutaoArgs);
redirectActivationPacket.ContentLength = jsonBytes.Length;
redirectActivationPacket.Checksum = XxHash64.HashToUInt64(jsonBytes);
clientStream.Write(new(&redirectActivationPacket, sizeof(PipePacketHeader)));
clientStream.Write(jsonBytes);
}
{
PipePacketHeader terminationPacket = default;
terminationPacket.Version = 1;
terminationPacket.Type = PipePacketType.Termination;
clientStream.Write(new(&terminationPacket, sizeof(PipePacketHeader)));
}
clientStream.Flush();
return true;
}
return false;
}
public void Dispose()
{
clientStream.Dispose();
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
[Injection(InjectAs.Singleton)]
[ConstructorGenerated]
internal sealed partial class PrivateNamedPipeMessageDispatcher
{
private readonly IServiceProvider serviceProvider;
public void RedirectActivation(HutaoActivationArguments? args)
{
if (args is null)
{
return;
}
serviceProvider.GetRequiredService<IActivation>().Activate(args);
}
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using System.IO.Hashing;
using System.IO.Pipes;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
[Injection(InjectAs.Singleton)]
[ConstructorGenerated]
internal sealed partial class PrivateNamedPipeServer : IDisposable
{
private readonly PrivateNamedPipeMessageDispatcher messageDispatcher;
private readonly NamedPipeServerStream serverStream = new("Snap.Hutao.PrivateNamedPipe", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
private readonly CancellationTokenSource serverTokenSource = new();
private readonly SemaphoreSlim serverSemaphore = new(1);
public void Dispose()
{
serverTokenSource.Cancel();
serverSemaphore.Wait();
serverSemaphore.Dispose();
serverTokenSource.Dispose();
serverStream.Dispose();
}
public async ValueTask RunAsync()
{
using (await serverSemaphore.EnterAsync(serverTokenSource.Token).ConfigureAwait(false))
{
while (!serverTokenSource.IsCancellationRequested)
{
try
{
await serverStream.WaitForConnectionAsync(serverTokenSource.Token).ConfigureAwait(false);
RunPacketSession(serverStream, serverTokenSource.Token);
}
catch (OperationCanceledException)
{
}
}
}
}
private static unsafe byte[] GetValidatedContent(NamedPipeServerStream serverStream, PipePacketHeader* header)
{
byte[] content = new byte[header->ContentLength];
serverStream.ReadAtLeast(content, header->ContentLength, false);
ThrowHelper.InvalidDataIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
return content;
}
private unsafe void RunPacketSession(NamedPipeServerStream serverStream, CancellationToken token)
{
Span<byte> headerSpan = stackalloc byte[sizeof(PipePacketHeader)];
bool sessionTerminated = false;
while (serverStream.IsConnected && !sessionTerminated && !token.IsCancellationRequested)
{
serverStream.ReadExactly(headerSpan);
fixed (byte* pHeader = headerSpan)
{
PipePacketHeader* header = (PipePacketHeader*)pHeader;
switch ((header->Type, header->Command, header->ContentType))
{
case (PipePacketType.Request, PipePacketCommand.RedirectActivation, PipePacketContentType.Json):
ReadOnlySpan<byte> content = GetValidatedContent(serverStream, header);
messageDispatcher.RedirectActivation(JsonSerializer.Deserialize<HutaoActivationArguments>(content));
break;
case (PipePacketType.Termination, _, _):
serverStream.Disconnect();
sessionTerminated = true;
return;
}
}
}
}
}

View File

@@ -2,9 +2,9 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using static Windows.Win32.PInvoke;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Console;
using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Core.Logging;
@@ -26,7 +26,7 @@ internal sealed class ConsoleWindowLifeTime : IDisposable
SetConsoleMode(inputHandle, mode);
}
SetConsoleTitle("Snap Hutao Debug Console");
SetConsoleTitleW("Snap Hutao Debug Console");
}
}
}

View File

@@ -14,113 +14,99 @@ namespace Snap.Hutao.Core;
[Injection(InjectAs.Singleton)]
internal sealed class RuntimeOptions
{
private readonly bool isWebView2Supported;
private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected;
private readonly Lazy<(Version Version, string UserAgent)> lazyVersionAndUserAgent = new(() =>
{
Version version = Package.Current.Id.Version.ToVersion();
return (version, $"Snap Hutao/{version}");
});
private bool? isElevated;
private readonly Lazy<string> lazyDataFolder = new(() =>
{
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
if (!string.IsNullOrEmpty(preferredPath))
{
Directory.CreateDirectory(preferredPath);
return preferredPath;
}
// Fallback to MyDocuments
string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#if RELEASE
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
#else
// 使得迁移能正常生成
string folderName = "Hutao";
#endif
string path = Path.GetFullPath(Path.Combine(myDocuments, folderName));
Directory.CreateDirectory(path);
return path;
});
private readonly Lazy<string> lazyDeviceId = new(() =>
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
return Convert.ToMd5HexString($"{userName}{machineGuid}");
});
private readonly Lazy<(string Version, bool Supported)> lazyWebViewEnvironment = new(() =>
{
try
{
string version = CoreWebView2Environment.GetAvailableBrowserVersionString();
return (version, true);
}
catch (FileNotFoundException)
{
return (SH.CoreWebView2HelperVersionUndetected, false);
}
});
private readonly Lazy<bool> lazyElevated = new(() =>
{
if (LocalSetting.Get(SettingKeys.OverrideElevationRequirement, false))
{
return true;
}
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
});
private readonly Lazy<string> lazyLocalCache = new(() => ApplicationData.Current.LocalCacheFolder.Path);
private readonly Lazy<string> lazyInstalledLocation = new(() => Package.Current.InstalledLocation.Path);
private readonly Lazy<string> lazyFamilyName = new(() => Package.Current.Id.FamilyName);
public RuntimeOptions(ILogger<RuntimeOptions> logger)
{
AppLaunchTime = DateTimeOffset.UtcNow;
DataFolder = GetDataFolderPath();
LocalCache = ApplicationData.Current.LocalCacheFolder.Path;
InstalledLocation = Package.Current.InstalledLocation.Path;
FamilyName = Package.Current.Id.FamilyName;
Version = Package.Current.Id.Version.ToVersion();
UserAgent = $"Snap Hutao/{Version}";
DeviceId = GetUniqueUserId();
DetectWebView2Environment(logger, out webView2Version, out isWebView2Supported);
static string GetDataFolderPath()
{
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
if (!string.IsNullOrEmpty(preferredPath))
{
Directory.CreateDirectory(preferredPath);
return preferredPath;
}
// Fallback to MyDocuments
string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#if RELEASE
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
#else
// 使得迁移能正常生成
string folderName = "Hutao";
#endif
string path = Path.GetFullPath(Path.Combine(myDocuments, folderName));
Directory.CreateDirectory(path);
return path;
}
static string GetUniqueUserId()
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
return Convert.ToMd5HexString($"{userName}{machineGuid}");
}
static void DetectWebView2Environment(ILogger<RuntimeOptions> logger, out string webView2Version, out bool isWebView2Supported)
{
try
{
webView2Version = CoreWebView2Environment.GetAvailableBrowserVersionString();
isWebView2Supported = true;
}
catch (FileNotFoundException ex)
{
webView2Version = SH.CoreWebView2HelperVersionUndetected;
isWebView2Supported = false;
logger.LogError(ex, "WebView2 Runtime not installed.");
}
}
}
public Version Version { get; }
public Version Version { get => lazyVersionAndUserAgent.Value.Version; }
public string UserAgent { get; }
public string UserAgent { get => lazyVersionAndUserAgent.Value.UserAgent; }
public string InstalledLocation { get; }
public string InstalledLocation { get => lazyInstalledLocation.Value; }
public string DataFolder { get; }
public string DataFolder { get => lazyDataFolder.Value; }
public string LocalCache { get; }
public string LocalCache { get => lazyLocalCache.Value; }
public string FamilyName { get; }
public string FamilyName { get => lazyFamilyName.Value; }
public string DeviceId { get; }
public string DeviceId { get => lazyDeviceId.Value; }
public string WebView2Version { get => webView2Version; }
public string WebView2Version { get => lazyWebViewEnvironment.Value.Version; }
public bool IsWebView2Supported { get => isWebView2Supported; }
public bool IsWebView2Supported { get => lazyWebViewEnvironment.Value.Supported; }
public bool IsElevated
{
get
{
return isElevated ??= GetElevated();
static bool GetElevated()
{
if (LocalSetting.Get(SettingKeys.OverrideElevationRequirement, false))
{
return true;
}
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}
}
}
public bool IsElevated { get => lazyElevated.Value; }
public DateTimeOffset AppLaunchTime { get; }
}

View File

@@ -22,7 +22,7 @@ internal static class SettingKeys
public const string DataFolderPath = "DataFolderPath";
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled";
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
#endregion
#region Passport
@@ -56,5 +56,11 @@ internal static class SettingKeys
public const string SuppressMetadataInitialization = "SuppressMetadataInitialization";
public const string OverrideElevationRequirement = "OverrideElevationRequirement";
public const string OverrideUpdateVersionComparison = "OverrideUpdateVersionComparison";
public const string OverridePackageConvertDirectoryPermissionsRequirement = "OverridePackageConvertDirectoryPermissionsRequirement";
#endregion
#region Obsolete
[Obsolete("重置调试控制台开关")]
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
#endregion
}

View File

@@ -68,7 +68,7 @@ internal sealed class ScheduleTaskInterop : IScheduleTaskInterop
public bool IsDailyNoteRefreshEnabled()
{
return WScriptExists(DailyNoteRefreshScriptName, out _);
return TaskService.Instance.RootFolder.Tasks.Any(task => task.Name is DailyNoteRefreshTaskName);
}
/// <summary>

View File

@@ -1,15 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.UI.Shell;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.IO;
using System.Runtime.InteropServices;
using Windows.Storage;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.Macros;
using static Snap.Hutao.Win32.Ole32;
namespace Snap.Hutao.Core.Shell;
@@ -34,31 +33,44 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
return false;
}
HRESULT result = CoCreateInstance<ShellLink, IShellLinkW>(null, CLSCTX.CLSCTX_INPROC_SERVER, out IShellLinkW shellLink);
Marshal.ThrowExceptionForHR(result);
return UnsafeTryCreateDesktopShoutcutForElevatedLaunch(targetLogoPath);
}
shellLink.SetPath($"shell:AppsFolder\\{runtimeOptions.FamilyName}!App");
shellLink.SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL);
shellLink.SetIconLocation(targetLogoPath, 0);
private unsafe bool UnsafeTryCreateDesktopShoutcutForElevatedLaunch(string targetLogoPath)
{
bool result = false;
IShellLinkDataList shellLinkDataList = (IShellLinkDataList)shellLink;
shellLinkDataList.GetFlags(out uint flags);
flags |= (uint)SHELL_LINK_DATA_FLAGS.SLDF_RUNAS_USER;
shellLinkDataList.SetFlags(flags);
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string target = Path.Combine(desktop, $"{SH.FormatAppNameAndVersion(runtimeOptions.Version)}.lnk");
IPersistFile persistFile = (IPersistFile)shellLink;
try
// DO NOT revert if condition, COM interfaces need to be released properly
HRESULT hr = CoCreateInstance(in ShellLink.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IShellLinkW.IID, out IShellLinkW* pShellLink);
if (SUCCEEDED(hr))
{
persistFile.Save(target, false);
}
catch (UnauthorizedAccessException)
{
return false;
pShellLink->SetPath($"shell:AppsFolder\\{runtimeOptions.FamilyName}!App");
pShellLink->SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL);
pShellLink->SetIconLocation(targetLogoPath, 0);
if (SUCCEEDED(pShellLink->QueryInterface(in IShellLinkDataList.IID, out IShellLinkDataList* pShellLinkDataList)))
{
pShellLinkDataList->GetFlags(out uint flags);
pShellLinkDataList->SetFlags(flags | (uint)SHELL_LINK_DATA_FLAGS.SLDF_RUNAS_USER);
pShellLinkDataList->Release();
}
if (SUCCEEDED(pShellLink->QueryInterface(in IPersistFile.IID, out IPersistFile* pPersistFile)))
{
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string target = Path.Combine(desktop, $"{SH.FormatAppNameAndVersion(runtimeOptions.Version)}.lnk");
if (SUCCEEDED(pPersistFile->Save(target, false)))
{
result = true;
}
pPersistFile->Release();
}
pShellLink->Release();
}
return true;
return result;
}
}

View File

@@ -8,12 +8,12 @@ internal readonly struct Delay
/// <summary>
/// 随机延迟
/// </summary>
/// <param name="minMilliSeconds">最小,闭</param>
/// <param name="maxMilliSeconds">最小,开</param>
/// <param name="min">最小,闭</param>
/// <param name="max">最小,开</param>
/// <returns>任务</returns>
public static ValueTask Random(int minMilliSeconds, int maxMilliSeconds)
public static ValueTask RandomMilliSeconds(int min, int max)
{
return Task.Delay((int)(System.Random.Shared.NextDouble() * (maxMilliSeconds - minMilliSeconds)) + minMilliSeconds).AsValueTask();
return Task.Delay((int)(System.Random.Shared.NextDouble() * (max - min)) + min).AsValueTask();
}
public static ValueTask FromSeconds(int seconds)

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Windowing;
using Snap.Hutao.Win32;
using Windows.Graphics;
namespace Snap.Hutao.Core.Windowing;

View File

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

View File

@@ -0,0 +1,65 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
namespace Snap.Hutao.Core.Windowing.Backdrop;
internal sealed class TransparentBackdrop : SystemBackdrop, IDisposable, IBackdropNeedEraseBackground
{
private readonly object compositorLock = new();
private Color tintColor;
private Windows.UI.Composition.CompositionColorBrush? brush;
private Windows.UI.Composition.Compositor? compositor;
public TransparentBackdrop()
: this(Colors.Transparent)
{
}
public TransparentBackdrop(Color tintColor)
{
this.tintColor = tintColor;
}
internal Windows.UI.Composition.Compositor Compositor
{
get
{
if (compositor is null)
{
lock (compositorLock)
{
if (compositor is null)
{
DispatcherQueue.EnsureSystemDispatcherQueue();
compositor = new Windows.UI.Composition.Compositor();
}
}
}
return compositor;
}
}
public void Dispose()
{
compositor?.Dispose();
}
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop connectedTarget, XamlRoot xamlRoot)
{
brush ??= Compositor.CreateColorBrush(tintColor);
connectedTarget.SystemBackdrop = brush;
}
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop disconnectedTarget)
{
disconnectedTarget.SystemBackdrop = null;
}
}

View File

@@ -9,6 +9,8 @@ namespace Snap.Hutao.Core.Windowing;
[HighQuality]
internal enum BackdropType
{
Transparent = -1,
/// <summary>
/// 无
/// </summary>

View File

@@ -5,11 +5,11 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
using System.Text;
using Windows.System;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing.HotKey;

View File

@@ -1,9 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
using System.Runtime.InteropServices;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing.HotKey;
@@ -14,7 +14,7 @@ internal sealed partial class HotKeyController : IHotKeyController
{
private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever;
private readonly object locker = new();
private readonly object syncRoot = new();
private readonly HotKeyOptions hotKeyOptions;
@@ -40,7 +40,8 @@ internal sealed partial class HotKeyController : IHotKeyController
private static unsafe INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags)
{
INPUT input = new() { type = INPUT_TYPE.INPUT_MOUSE, };
INPUT input = default;
input.type = INPUT_TYPE.INPUT_MOUSE;
input.Anonymous.mi.dwFlags = flags;
return input;
}
@@ -75,7 +76,7 @@ internal sealed partial class HotKeyController : IHotKeyController
private void ToggleMouseClickRepeatForever()
{
lock (locker)
lock (syncRoot)
{
if (hotKeyOptions.IsMouseClickRepeatForeverOn)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
using Windows.System;
using Windows.Win32.UI.Input.KeyboardAndMouse;
namespace Snap.Hutao.Core.Windowing.HotKey;

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.Win32.UI.WindowsAndMessaging;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao.Core.Windowing;

View File

@@ -9,13 +9,15 @@ using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service;
using Snap.Hutao.Win32;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Dwm;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.IO;
using Windows.Graphics;
using Windows.UI;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.DwmApi;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
@@ -53,10 +55,10 @@ internal sealed class WindowController
private void InitializeCore()
{
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
window.AppWindow.Title = SH.FormatAppNameAndVersion(hutaoOptions.Version);
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
window.AppWindow.Title = SH.FormatAppNameAndVersion(runtimeOptions.Version);
window.AppWindow.SetIcon(Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico"));
ExtendsContentIntoTitleBar();
RecoverOrInitWindowSize();
@@ -104,11 +106,11 @@ internal sealed class WindowController
return;
}
WINDOWPLACEMENT windowPlacement = Win32.StructMarshal.WINDOWPLACEMENT();
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create();
GetWindowPlacement(options.Hwnd, ref windowPlacement);
// prevent save value when we are maximized.
if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
if (!windowPlacement.ShowCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
{
double scale = 1.0 / options.GetRasterizationScale();
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect().Scale(scale));
@@ -157,6 +159,7 @@ internal sealed class WindowController
{
window.SystemBackdrop = backdropType switch
{
BackdropType.Transparent => new Backdrop.TransparentBackdrop(),
BackdropType.MicaAlt => new MicaBackdrop() { Kind = MicaKind.BaseAlt },
BackdropType.Mica => new MicaBackdrop() { Kind = MicaKind.Base },
BackdropType.Acrylic => new DesktopAcrylicBackdrop(),
@@ -193,7 +196,7 @@ internal sealed class WindowController
private unsafe void UpdateImmersiveDarkMode(FrameworkElement titleBar, object discard)
{
BOOL isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(titleBar.ActualTheme);
DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &isDarkMode, unchecked((uint)sizeof(BOOL)));
DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, ref isDarkMode);
}
private void UpdateDragRectangles()

View File

@@ -2,7 +2,11 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.Runtime.CompilerServices;
using WinRT.Interop;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
@@ -16,4 +20,12 @@ internal static class WindowExtension
WindowController windowController = new(window, window.WindowOptions, serviceProvider);
WindowControllers.Add(window, windowController);
}
public static void SetLayeredWindow(this Window window)
{
HWND hwnd = (HWND)WindowNative.GetWindowHandle(window);
nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED;
SetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style);
}
}

View File

@@ -4,10 +4,10 @@
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Snap.Hutao.Win32.Foundation;
using Windows.Graphics;
using Windows.Win32.Foundation;
using WinRT.Interop;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
@@ -55,7 +55,7 @@ internal readonly struct WindowOptions
/// <param name="persistSize">持久化尺寸</param>
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false)
{
Hwnd = (HWND)WindowNative.GetWindowHandle(window);
Hwnd = WindowNative.GetWindowHandle(window);
InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id);
TitleBar = titleBar;
InitSize = initSize;
@@ -80,8 +80,8 @@ internal readonly struct WindowOptions
{
HWND fgHwnd = GetForegroundWindow();
uint threadIdHwnd = GetWindowThreadProcessId(Hwnd);
uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd);
uint threadIdHwnd = GetWindowThreadProcessId(Hwnd, default);
uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd, default);
if (threadIdHwnd != threadIdFgHwnd)
{

View File

@@ -2,11 +2,14 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing.Backdrop;
using Snap.Hutao.Core.Windowing.HotKey;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.UI.Shell;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using static Snap.Hutao.Win32.ComCtl32;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
@@ -25,8 +28,8 @@ internal sealed class WindowSubclass : IDisposable
private readonly IHotKeyController hotKeyController;
// We have to explicitly hold a reference to SUBCLASSPROC
private SUBCLASSPROC? windowProc;
private SUBCLASSPROC? legacyDragBarProc;
private SUBCLASSPROC windowProc = default!;
private SUBCLASSPROC legacyDragBarProc = default!;
public WindowSubclass(Window window, in WindowOptions options, IServiceProvider serviceProvider)
{
@@ -56,7 +59,7 @@ internal sealed class WindowSubclass : IDisposable
}
titleBarHooked = false;
HWND hwndDragBar = FindWindowEx(options.Hwnd, default, "DRAG_BAR_WINDOW_CLASS", default);
HWND hwndDragBar = FindWindowExW(options.Hwnd, default, "DRAG_BAR_WINDOW_CLASS", default);
if (hwndDragBar.IsNull)
{
@@ -75,12 +78,12 @@ internal sealed class WindowSubclass : IDisposable
hotKeyController.UnregisterAll();
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
windowProc = null;
windowProc = default!;
if (options.UseLegacyDragBarImplementation)
{
RemoveWindowSubclass(options.Hwnd, legacyDragBarProc, DragBarSubclassId);
legacyDragBarProc = null;
legacyDragBarProc = default!;
}
}
@@ -93,7 +96,7 @@ internal sealed class WindowSubclass : IDisposable
{
if (window is IMinMaxInfoHandler handler)
{
handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam.Value, options.GetRasterizationScale());
handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, options.GetRasterizationScale());
}
break;
@@ -110,6 +113,16 @@ internal sealed class WindowSubclass : IDisposable
hotKeyController.OnHotKeyPressed(*(HotKeyParameter*)&lParam);
break;
}
case WM_ERASEBKGND:
{
if (window.SystemBackdrop is IBackdropNeedEraseBackground)
{
return (LRESULT)(int)BOOL.TRUE;
}
break;
}
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);

View File

@@ -27,18 +27,4 @@ internal static class StringExtension
{
return source.AsSpan().TrimEnd(value).ToString();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsAny(this string source, in ReadOnlySpan<string> values, StringComparison comparisonType)
{
foreach (ref readonly string value in values)
{
if (source.Equals(value, comparisonType))
{
return true;
}
}
return false;
}
}

View File

@@ -3,13 +3,14 @@
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.UI.Shell;
using Snap.Hutao.Win32.UI.Shell.Common;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.Shell.Common;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.Macros;
using static Snap.Hutao.Win32.Ole32;
using static Snap.Hutao.Win32.Shell32;
namespace Snap.Hutao.Factory.Picker;
@@ -21,32 +22,33 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
public unsafe ValueResult<bool, ValueFile> PickFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
{
CoCreateInstance<FileOpenDialog, IFileOpenDialog>(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileOpenDialog dialog).ThrowOnFailure();
HRESULT hr = CoCreateInstance(in FileOpenDialog.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileDialog.IID, out IFileDialog* pFileDialog);
Marshal.ThrowExceptionForHR(hr);
FILEOPENDIALOGOPTIONS options =
FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM |
FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR;
dialog.SetOptions(options);
SetDesktopAsStartupFolder(dialog);
if (!string.IsNullOrEmpty(title))
{
dialog.SetTitle(title);
}
pFileDialog->SetOptions(options);
SetDesktopAsStartupFolder(pFileDialog);
if (!string.IsNullOrEmpty(defaultFileName))
{
dialog.SetFileName(defaultFileName);
pFileDialog->SetFileName(defaultFileName);
}
if (!string.IsNullOrEmpty(title))
{
pFileDialog->SetTitle(title);
}
if (filters is { Length: > 0 })
{
SetFileTypes(dialog, filters);
SetFileTypes(pFileDialog, filters);
}
HRESULT res = dialog.Show(currentWindowReference.GetWindowHandle());
HRESULT res = pFileDialog->Show(currentWindowReference.GetWindowHandle());
if (res == HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_CANCELLED))
{
return new(false, default);
@@ -56,18 +58,18 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
Marshal.ThrowExceptionForHR(res);
}
dialog.GetResult(out IShellItem item);
HRESULT t = pFileDialog->GetResult(out IShellItem* pShellItem);
PWSTR displayName = default;
string file;
try
{
item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
file = new((char*)displayName);
pShellItem->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
file = new(displayName);
}
finally
{
Marshal.FreeCoTaskMem((nint)displayName.Value);
CoTaskMemFree(displayName);
}
return new(true, file);
@@ -75,7 +77,8 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
public unsafe ValueResult<bool, ValueFile> SaveFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters)
{
CoCreateInstance<FileSaveDialog, IFileSaveDialog>(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileSaveDialog dialog).ThrowOnFailure();
HRESULT hr = CoCreateInstance(in FileSaveDialog.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileDialog.IID, out IFileDialog* pFileDialog);
Marshal.ThrowExceptionForHR(hr);
FILEOPENDIALOGOPTIONS options =
FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
@@ -83,25 +86,25 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
FILEOPENDIALOGOPTIONS.FOS_STRICTFILETYPES |
FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR;
dialog.SetOptions(options);
SetDesktopAsStartupFolder(dialog);
if (!string.IsNullOrEmpty(title))
{
dialog.SetTitle(title);
}
pFileDialog->SetOptions(options);
SetDesktopAsStartupFolder(pFileDialog);
if (!string.IsNullOrEmpty(defaultFileName))
{
dialog.SetFileName(defaultFileName);
pFileDialog->SetFileName(defaultFileName);
}
if (!string.IsNullOrEmpty(title))
{
pFileDialog->SetTitle(title);
}
if (filters is { Length: > 0 })
{
SetFileTypes(dialog, filters);
SetFileTypes(pFileDialog, filters);
}
HRESULT res = dialog.Show(currentWindowReference.GetWindowHandle());
HRESULT res = pFileDialog->Show(currentWindowReference.GetWindowHandle());
if (res == HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_CANCELLED))
{
return new(false, default);
@@ -111,18 +114,18 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
Marshal.ThrowExceptionForHR(res);
}
dialog.GetResult(out IShellItem item);
pFileDialog->GetResult(out IShellItem* pShellItem);
PWSTR displayName = default;
string file;
try
{
item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
file = new((char*)displayName);
pShellItem->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
file = new(displayName);
}
finally
{
Marshal.FreeCoTaskMem((nint)displayName.Value);
CoTaskMemFree(displayName);
}
return new(true, file);
@@ -130,7 +133,8 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
public unsafe ValueResult<bool, string> PickFolder(string? title)
{
CoCreateInstance<FileOpenDialog, IFileOpenDialog>(default, CLSCTX.CLSCTX_INPROC_SERVER, out IFileOpenDialog dialog).ThrowOnFailure();
HRESULT hr = CoCreateInstance(in FileOpenDialog.CLSID, default, CLSCTX.CLSCTX_INPROC_SERVER, in IFileDialog.IID, out IFileDialog* pFileDialog);
Marshal.ThrowExceptionForHR(hr);
FILEOPENDIALOGOPTIONS options =
FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
@@ -138,15 +142,15 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS |
FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR;
dialog.SetOptions(options);
SetDesktopAsStartupFolder(dialog);
pFileDialog->SetOptions(options);
SetDesktopAsStartupFolder(pFileDialog);
if (!string.IsNullOrEmpty(title))
{
dialog.SetTitle(title);
pFileDialog->SetTitle(title);
}
HRESULT res = dialog.Show(currentWindowReference.GetWindowHandle());
HRESULT res = pFileDialog->Show(currentWindowReference.GetWindowHandle());
if (res == HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_CANCELLED))
{
return new(false, default!);
@@ -156,25 +160,24 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
Marshal.ThrowExceptionForHR(res);
}
dialog.GetResult(out IShellItem item);
pFileDialog->GetResult(out IShellItem* pShellItem);
PWSTR displayName = default;
string file;
try
{
item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
pShellItem->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out displayName);
file = new((char*)displayName);
}
finally
{
Marshal.FreeCoTaskMem((nint)displayName.Value);
CoTaskMemFree(displayName);
}
return new(true, file);
}
private static unsafe void SetFileTypes<TDialog>(TDialog dialog, (string Name, string Type)[] filters)
where TDialog : IFileDialog
private static unsafe void SetFileTypes(IFileDialog* pFileDialog, (string Name, string Type)[] filters)
{
List<nint> unmanagedStringPtrs = new(filters.Length * 2);
List<COMDLG_FILTERSPEC> filterSpecs = new(filters.Length);
@@ -190,10 +193,7 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
filterSpecs.Add(spec);
}
fixed (COMDLG_FILTERSPEC* ptr = CollectionsMarshal.AsSpan(filterSpecs))
{
dialog.SetFileTypes((uint)filterSpecs.Count, ptr);
}
pFileDialog->SetFileTypes(CollectionsMarshal.AsSpan(filterSpecs));
foreach (ref readonly nint ptr in CollectionsMarshal.AsSpan(unmanagedStringPtrs))
{
@@ -201,11 +201,11 @@ internal sealed partial class FileSystemPickerInteraction : IFileSystemPickerInt
}
}
private static unsafe void SetDesktopAsStartupFolder<TDialog>(TDialog dialog)
where TDialog : IFileDialog
private static unsafe void SetDesktopAsStartupFolder(IFileDialog* pFileDialog)
{
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
SHCreateItemFromParsingName(desktopPath, default, typeof(IShellItem).GUID, out object shellItem).ThrowOnFailure();
dialog.SetFolder((IShellItem)shellItem);
HRESULT hr = SHCreateItemFromParsingName(desktopPath, default, in IShellItem.IID, out IShellItem* pShellItem);
Marshal.ThrowExceptionForHR(hr);
pFileDialog->SetFolder(pShellItem);
}
}

View File

@@ -3,7 +3,7 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Windows.Win32.UI.WindowsAndMessaging;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao;
@@ -32,9 +32,9 @@ internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMa
public unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor)
{
info.ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, info.ptMinTrackSize.X);
info.ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, info.ptMinTrackSize.Y);
info.ptMaxTrackSize.X = (int)Math.Min(MaxWidth * scalingFactor, info.ptMaxTrackSize.X);
info.ptMaxTrackSize.Y = (int)Math.Min(MaxHeight * scalingFactor, info.ptMaxTrackSize.Y);
info.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, info.ptMinTrackSize.x);
info.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, info.ptMinTrackSize.y);
info.ptMaxTrackSize.x = (int)Math.Min(MaxWidth * scalingFactor, info.ptMaxTrackSize.x);
info.ptMaxTrackSize.y = (int)Math.Min(MaxHeight * scalingFactor, info.ptMaxTrackSize.y);
}
}
}

View File

@@ -3,7 +3,7 @@
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core;
using Snap.Hutao.Win32;
using Windows.Graphics;
namespace Snap.Hutao;

View File

@@ -4,7 +4,7 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.ViewModel.Game;
using Windows.Win32.UI.WindowsAndMessaging;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao;
@@ -50,9 +50,9 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
/// <inheritdoc/>
public unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor)
{
info.ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, info.ptMinTrackSize.X);
info.ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, info.ptMinTrackSize.Y);
info.ptMaxTrackSize.X = (int)Math.Min(MaxWidth * scalingFactor, info.ptMaxTrackSize.X);
info.ptMaxTrackSize.Y = (int)Math.Min(MaxHeight * scalingFactor, info.ptMaxTrackSize.Y);
info.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, info.ptMinTrackSize.x);
info.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, info.ptMinTrackSize.y);
info.ptMaxTrackSize.x = (int)Math.Min(MaxWidth * scalingFactor, info.ptMaxTrackSize.x);
info.ptMaxTrackSize.y = (int)Math.Min(MaxHeight * scalingFactor, info.ptMaxTrackSize.y);
}
}

View File

@@ -2,18 +2,18 @@
x:Class="Snap.Hutao.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shv="using:Snap.Hutao.View"
mc:Ignorable="d">
<Grid>
<shv:MainView/>
<shv:TitleView
x:Name="TitleBarView"
Height="44"
Margin="48,0,0,0"/>
Margin="44,0,0,0"/>
<shv:MainView/>
</Grid>
</Window>

View File

@@ -3,7 +3,7 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Windows.Win32.UI.WindowsAndMessaging;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao;
@@ -37,7 +37,7 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
/// <inheritdoc/>
public unsafe void HandleMinMaxInfo(ref MINMAXINFO pInfo, double scalingFactor)
{
pInfo.ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, pInfo.ptMinTrackSize.X);
pInfo.ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, pInfo.ptMinTrackSize.Y);
pInfo.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, pInfo.ptMinTrackSize.x);
pInfo.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, pInfo.ptMinTrackSize.y);
}
}

View File

@@ -11,6 +11,12 @@ internal static class CollectionsNameValue
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(x.ToString(), x))];
}
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> codiction)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Where(codiction).Select(x => new NameValue<TEnum>(x.ToString(), x))];
}
public static List<NameValue<TSource>> From<TSource>(IEnumerable<TSource> sources, Func<TSource, string> nameSelector)
{
return [.. sources.Select(x => new NameValue<TSource>(nameSelector(x), x))];

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("game_accounts")]
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>, ICloneable<GameAccount>
{
/// <summary>
/// 内部Id
@@ -54,6 +54,17 @@ internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount,
};
}
public GameAccount Clone()
{
return new()
{
AttachUid = AttachUid,
Name = Name,
MihoyoSDK = MihoyoSDK,
Type = Type,
};
}
/// <summary>
/// 更新绑定的Uid
/// </summary>

View File

@@ -3,7 +3,7 @@
using Snap.Hutao.Core;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Model.InterChange.GachaLog;
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.InterChange.GachaLog;
/// UIGF格式的信息
/// </summary>
[HighQuality]
internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, MetadataOptions, string>
internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, CultureOptions, string>
{
/// <summary>
/// 用户Uid
@@ -65,12 +65,12 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
[JsonPropertyName("region_time_zone")]
public int? RegionTimeZone { get; set; } = default!;
public static UIGFInfo From(RuntimeOptions runtimeOptions, MetadataOptions metadataOptions, string uid)
public static UIGFInfo From(RuntimeOptions runtimeOptions, CultureOptions cultureOptions, string uid)
{
return new()
{
Uid = uid,
Language = metadataOptions.LanguageCode,
Language = cultureOptions.LanguageCode,
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
ExportApp = SH.AppName,
ExportAppVersion = runtimeOptions.Version.ToString(),

View File

@@ -64,14 +64,14 @@ internal sealed class UIIFInfo
/// <returns>专用 UIGF 信息</returns>
public static UIIFInfo From(IServiceProvider serviceProvider, string uid)
{
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return new()
{
Uid = uid,
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
ExportApp = SH.AppName,
ExportAppVersion = hutaoOptions.Version.ToString(),
ExportAppVersion = runtimeOptions.Version.ToString(),
UIIFVersion = UIIF.CurrentVersion,
};
}

View File

@@ -2,7 +2,9 @@
// Licensed under the MIT license.
using Snap.Hutao.Control;
using Snap.Hutao.Control.Text;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Primitive;
using System.Globalization;
@@ -50,8 +52,20 @@ internal sealed partial class DescriptionsParametersDescriptor : ValueConverter<
{
if (desc.AsSpan().TrySplitIntoTwo('|', out ReadOnlySpan<char> description, out ReadOnlySpan<char> format))
{
string resultFormatted = ParamRegex().Replace(format.ToString(), match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = description.ToString(), Parameter = resultFormatted });
if (description[0] is not '#')
{
// Fast path
string resultFormatted = ParamRegex().Replace(format.ToString(), match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = description.ToString(), Parameter = resultFormatted });
}
else
{
string descriptionString = SpecialNameHandler.Handle(description.ToString());
string formatString = SpecialNameHandler.Handle(format.ToString());
string resultFormatted = ParamRegex().Replace(formatString, match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = descriptionString, Parameter = resultFormatted });
}
}
else
{

View File

@@ -3,8 +3,8 @@
using Microsoft.UI;
using Snap.Hutao.Control;
using Snap.Hutao.Core;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Win32;
using Windows.UI;
namespace Snap.Hutao.Model.Metadata.Converter;

View File

@@ -13,43 +13,118 @@ internal static class Materials
{
public static FrozenSet<MaterialId> MondayThursdayItems { get; } = FrozenSet.ToFrozenSet<MaterialId>(
[
104301U, 104302U, 104303U, // 「自由」
104310U, 104311U, 104312U, // 「繁荣」
104320U, 104321U, 104322U, // 「浮世
104329U, 104330U, 104331U, // 「诤言」
104338U, 104339U, 104340U, // 「公平」
114001U, 114002U, 114003U, 114004U, // 高塔孤王
114013U, 114014U, 114015U, 114016U, // 孤云寒林
114025U, 114026U, 114027U, 114028U, // 远海夷地
114037U, 114038U, 114039U, 114040U, // 谧林涓露
114049U, 114050U, 114051U, 114052U, // 悠古弦音
104301U,
104302U,
104303U, // 「自由
104310U,
104311U,
104312U, // 「繁荣」
104320U,
104321U,
104322U, // 「浮世」
104329U,
104330U,
104331U, // 「诤言」
104338U,
104339U,
104340U, // 「公平」
114001U,
114002U,
114003U,
114004U, // 高塔孤王
114013U,
114014U,
114015U,
114016U, // 孤云寒林
114025U,
114026U,
114027U,
114028U, // 远海夷地
114037U,
114038U,
114039U,
114040U, // 谧林涓露
114049U,
114050U,
114051U,
114052U, // 悠古弦音
]);
public static FrozenSet<MaterialId> TuesdayFridayItems { get; } = FrozenSet.ToFrozenSet<MaterialId>(
[
104304U, 104305U, 104306U, // 「抗争」
104313U, 104314U, 104315U, // 「勤劳」
104323U, 104324U, 104325U, // 「风雅
104332U, 104333U, 104334U, // 「巧思」
104341U, 104342U, 104343U, // 「正义」
114005U, 114006U, 114007U, 114008U, // 凛风奔狼
114017U, 114018U, 114019U, 114020U, // 雾海云间
114029U, 114030U, 114031U, 114032U, // 鸣神御灵
114041U, 114042U, 114043U, 114044U, // 绿洲花园
114053U, 114054U, 114055U, 114056U, // 纯圣露滴
104304U,
104305U,
104306U, // 「抗争
104313U,
104314U,
104315U, // 「勤劳」
104323U,
104324U,
104325U, // 「风雅」
104332U,
104333U,
104334U, // 「巧思」
104341U,
104342U,
104343U, // 「正义」
114005U,
114006U,
114007U,
114008U, // 凛风奔狼
114017U,
114018U,
114019U,
114020U, // 雾海云间
114029U,
114030U,
114031U,
114032U, // 鸣神御灵
114041U,
114042U,
114043U,
114044U, // 绿洲花园
114053U,
114054U,
114055U,
114056U, // 纯圣露滴
]);
public static FrozenSet<MaterialId> WednesdaySaturdayItems { get; } = FrozenSet.ToFrozenSet<MaterialId>(
[
104307U, 104308U, 104309U, // 「诗文」
104316U, 104317U, 104318U, // 「黄金」
104326U, 104327U, 104328U, // 「天光
104335U, 104336U, 104337U, // 「笃行」
104344U, 104345U, 104346U, // 「秩序」
114009U, 114010U, 114011U, 114012U, // 狮牙斗士
114021U, 114022U, 114023U, 114024U, // 漆黑陨铁
114033U, 114034U, 114035U, 114036U, // 今昔剧画
114045U, 114046U, 114047U, 114048U, // 谧林涓露
114057U, 114058U, 114059U, 114060U, // 无垢之海
104307U,
104308U,
104309U, // 「诗文
104316U,
104317U,
104318U, // 「黄金」
104326U,
104327U,
104328U, // 「天光」
104335U,
104336U,
104337U, // 「笃行」
104344U,
104345U,
104346U, // 「秩序」
114009U,
114010U,
114011U,
114012U, // 狮牙斗士
114021U,
114022U,
114023U,
114024U, // 漆黑陨铁
114033U,
114034U,
114035U,
114036U, // 今昔剧画
114045U,
114046U,
114047U,
114048U, // 谧林涓露
114057U,
114058U,
114059U,
114060U, // 无垢之海
]);
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text;
using System.Text.RegularExpressions;
namespace Snap.Hutao.Metadata;
internal static partial class SpecialNameHandler
{
// Use this regex to query special names in metadata
// "#(?!.*(?:F#|M#|NON_BREAK_SPACE|REALNAME\[ID\(1\)(\|HOSTONLY\(true\)|)\]|\{LAYOUT_MOBILE#.+?\}\{LAYOUT_PC#.+?\}\{LAYOUT_PS#.+?\})).*
public static string Handle(string input)
{
if (input.AsSpan()[0] is not '#')
{
return input;
}
StringBuilder resultBuilder = new(input);
resultBuilder
.Replace("{MATEAVATAR#SEXPRO[INFO_MALE_PRONOUN_BOYFIRST|INFO_FEMALE_PRONOUN_GIRLFIRST]}", SH.MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst)
.Replace("{MATEAVATAR#SEXPRO[INFO_MALE_PRONOUN_BOYD|INFO_FEMALE_PRONOUN_GIRLD]}", SH.MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD)
.Replace("{PLAYERAVATAR#SEXPRO[INFO_MALE_PRONOUN_HE|INFO_FEMALE_PRONOUN_SHE]}", SH.MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe)
.Replace("{REALNAME[ID(1)|HOSTONLY(true)]}", SH.MetadataSpecialNameRealNameId1)
.Replace("{REALNAME[ID(1)]}", SH.MetadataSpecialNameRealNameId1)
.Replace("{NICKNAME}", SH.MetadataSpecialNameNickname)
.Replace("{NON_BREAK_SPACE}", "\u00A0");
input = resultBuilder.ToString();
input = MaleFemaleRegex().Replace(input, "<color=#1E90FF>$1</color>/<color=#FFB6C1>$2</color>");
input = FemaleMaleRegex().Replace(input, "<color=#FFB6C1>$1</color>/<color=#1E90FF>$2</color>");
input = LayoutRegex().Replace(input, "$2");
return input[1..];
}
[GeneratedRegex("\\{M#(.*?)\\}\\{F#(.*?)\\}")]
private static partial Regex MaleFemaleRegex();
[GeneratedRegex("\\{F#(.*?)\\}\\{M#(.*?)\\}")]
private static partial Regex FemaleMaleRegex();
[GeneratedRegex("\\{LAYOUT_MOBILE#(.+?)\\}\\{LAYOUT_PC#(.+?)\\}\\{LAYOUT_PS#(.+?)\\}")]
private static partial Regex LayoutRegex();
}

View File

@@ -82,4 +82,7 @@ internal enum WaveType
[LocalizationKey(nameof(SH.ModelMetadataTowerWaveTypeWave1Additional))]
Wave1Additional = 1999,
[LocalizationKey(nameof(SH.ModelMetadataTowerWaveTypeWave99999))]
Wave99999 = 99999,
}

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.4.0" />
Version="1.9.5.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.4.0" />
Version="1.9.5.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>

View File

@@ -19,6 +19,22 @@ public static partial class Program
{
private static readonly ApplicationInitializationCallback AppInitializationCallback = InitializeApp;
[ModuleInitializer]
internal static void InitializeModule()
{
// Set base directory env var for PublishSingleFile support (referenced by SxS redirection)
Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", AppContext.BaseDirectory);
// No error handling needed as the target function does nothing (just {return S_OK}).
// It's the act of calling the function causing the DllImport to load the DLL that
// matters. This provides the moral equivalent of a native DLL's Import Address
// Table (IAT) have an entry that's resolved when this module is loaded.
_ = WindowsAppRuntimeEnsureIsLoaded();
}
[LibraryImport("Microsoft.WindowsAppRuntime.dll", EntryPoint = "WindowsAppRuntime_EnsureIsLoaded")]
private static partial int WindowsAppRuntimeEnsureIsLoaded();
[LibraryImport("Microsoft.ui.xaml.dll")]
private static partial void XamlCheckProcessRequirements();

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -247,7 +247,7 @@
<value>Not refreshed</value>
</data>
<data name="ModelEntityDailyNoteRefreshTimeFormat" xml:space="preserve">
<value>Refresh at {0:MM.dd HH:mm:ss}</value>
<value>Refreshed at {0:MM.dd HH:mm:ss}</value>
</data>
<data name="ModelEntitySpiralAbyssScheduleFormat" xml:space="preserve">
<value>Schedule {0}</value>
@@ -738,10 +738,10 @@
<value>Avatar Calculator: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>My Characters: N/A</value>
<value>Battle Chronicle: Not Refreshed</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>My Characters: {0:MM-dd HH:mm}</value>
<value>Battle Chronicle: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>Enka: N/A</value>
@@ -870,11 +870,23 @@
<value>No writing permission in file system, unable to start the server conversion.</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>Querying Game Resource Information</value>
<value>Download game resource index</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>Failed to operate on game file: {0}</value>
</data>
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
<value>Failed to unlock frame rate limit</value>
</data>
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
<value>Game in process</value>
</data>
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
<value>Select Game Path</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>Failed to download game resource index: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>Game process closed</value>
</data>
@@ -1158,7 +1170,7 @@
<value>Realm Currency Alarm Threshold</value>
</data>
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
<value>Realm Currency Alarm Threshold</value>
<value>Original Resin Alarm Threshold</value>
</data>
<data name="ViewDialogDailyNoteNotificationShowInHomeWidget" xml:space="preserve">
<value>Show widget on Home page</value>
@@ -1175,6 +1187,12 @@
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>Realtime Note Webhook URL</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>You need other tools to restore the loopback after it is exempted</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>Confirm to Config Loopback Exemption</value>
</data>
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
<value>Import wish history</value>
</data>
@@ -1271,6 +1289,12 @@
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
<value>Scan the QR code with MiHoYo BBS App</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>To avoid enable in a mistake, please input &lt;b&gt;title name&lt;/b&gt; of feature you are enabling</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>You are Enabling a Dangerous Feature</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>This action is irreversible, and all user login status will be lost</value>
</data>
@@ -1292,6 +1316,9 @@
<data name="ViewDialogUserTitle" xml:space="preserve">
<value>Set Cookie</value>
</data>
<data name="ViewFeedbackHeader" xml:space="preserve">
<value>Feedback Center</value>
</data>
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>Wish History</value>
</data>
@@ -1368,7 +1395,7 @@
<value>This operation is irreversible. The achievement archive will be lost.</value>
</data>
<data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve">
<value>Are you sure you want to delete archive {0}?</value>
<value>Are you sure to delete archive {0}?</value>
</data>
<data name="ViewModelAchievementUIAFExportPickerTitle" xml:space="preserve">
<value>Export UIAF Json file to the selected path</value>
@@ -1430,6 +1457,9 @@
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
<value>HoYoLab account does not support Realtime Notes verification</value>
</data>
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
<value>Failed to modify Scheduled Task</value>
</data>
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
<value>30 min | 3.75 Resin</value>
</data>
@@ -1445,14 +1475,11 @@
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
<value>8 min | 1 Resin</value>
</data>
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
<value>Failed to add Scheduled Task, please try again in administrator mode</value>
</data>
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
<value>Fetching Realtime Notes data, please wait</value>
</data>
<data name="ViewModelExportSuccessMessage" xml:space="preserve">
<value>Successfully saved to selected patch</value>
<value>Successfully saved to selected path</value>
</data>
<data name="ViewModelExportSuccessTitle" xml:space="preserve">
<value>Export successfully</value>
@@ -1491,7 +1518,7 @@
<value>Failed to fetch wish history</value>
</data>
<data name="ViewModelGachaLogRefreshOperationCancel" xml:space="preserve">
<value>Wish history refresh is canceled unexpectedly</value>
<value>Wish history refresh was canceled unexpectedly</value>
</data>
<data name="ViewModelGachaLogRemoveArchiveDescription" xml:space="preserve">
<value>This operation is irreversible. The wish history archive will be lost.</value>
@@ -1541,6 +1568,9 @@
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>Convert server failed</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>Identify Monitors</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>Unable to read game config file: {0}, file may be not exist not lack of user permission</value>
</data>
@@ -1574,6 +1604,12 @@
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>Failed to create desktop shortcut</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
<value>You will need to re-download needed files, are you sure to delete?</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
<value>Delete Sever Conversion Client Cache</value>
</data>
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
<value>Used disk space: {0}</value>
</data>
@@ -1626,7 +1662,7 @@
<value>Create New Archive</value>
</data>
<data name="ViewPageAchievementAddArchiveHint" xml:space="preserve">
<value>Create new archive to continue</value>
<value>Create New Archive to Continue</value>
</data>
<data name="ViewPageAchievementExportLabel" xml:space="preserve">
<value>Export</value>
@@ -1647,7 +1683,7 @@
<value>Name, description, version or ID</value>
</data>
<data name="ViewPageAchievementSortIncompletedItemsFirst" xml:space="preserve">
<value>Prefer incomplete</value>
<value>Prefer Incomplete</value>
</data>
<data name="ViewPageAnnouncementActivity" xml:space="preserve">
<value>Event Notice</value>
@@ -1671,7 +1707,7 @@
<value>CRIT Rating</value>
</data>
<data name="ViewPageAvatarPropertyDefaultDescription" xml:space="preserve">
<value>No character data fetched</value>
<value>No Character Data Fetched</value>
</data>
<data name="ViewPageAvatarPropertyExportAsImage" xml:space="preserve">
<value>Export as Image</value>
@@ -1698,7 +1734,7 @@
<value>Sync character talents data</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>Sync from MiHoYo BBS My Characters</value>
<value>Sync from MiHoYo BBS Battle Chronicle</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>Sync most data other than character talent</value>
@@ -1722,7 +1758,7 @@
<value>Create</value>
</data>
<data name="ViewPageCultivationAddProjectContinue" xml:space="preserve">
<value>Create plan to continue</value>
<value>Create Plan to Continue</value>
</data>
<data name="ViewPageCultivationAddProjectDescription" xml:space="preserve">
<value>You can add development plan items later from other pages</value>
@@ -1823,6 +1859,48 @@
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
<value>Verify Current User and Role</value>
</data>
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
<value>Search for questions and suggestions</value>
</data>
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
<value>Basic Information</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>Useful Links</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>Current Proxy</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>No Proxy</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>Exempted</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>Config Loopback Exemption</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>Keep in touch with us</value>
</data>
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
<value>Feature Documents</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>We always prioritize issues reported on GitHub</value>
</data>
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
<value>Development Roadmap</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>No Result</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>Snap Hutao Service Availability Monitor</value>
</data>
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
<value>Snap Hutao Services</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>Full Refresh</value>
</data>
@@ -1875,7 +1953,7 @@
<value>Input</value>
</data>
<data name="ViewPageGachaLogRecoverFromHutaoCloudDescription" xml:space="preserve">
<value>Recover Wish Record from Snap Hutao Cloud</value>
<value>Recover Wish Records from Snap Hutao Cloud</value>
</data>
<data name="ViewPageGachaLogRefresh" xml:space="preserve">
<value>Refresh</value>
@@ -2055,10 +2133,10 @@
<value>Borderless</value>
</data>
<data name="ViewPageLaunchGameAppearanceCloudThirdPartyMobileDescription" xml:space="preserve">
<value>Enable touchscreen layout, but the keyboard &amp; mouse are no longer usable.</value>
<value>Enable touchscreen layout, but the keyboard &amp; mouse are no longer usable</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveDescription" xml:space="preserve">
<value>Incompatible with embedded browsers; operations like switching window may cause the game to crash.</value>
<value>Incompatible with embedded browsers; operations like switching window may cause the game to crash</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveHeader" xml:space="preserve">
<value>Exclusive Fullscreen</value>
@@ -2088,7 +2166,7 @@
<value>Modify its default behavior at game startup</value>
</data>
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Start-up Arguments</value>
<value>Launch Parameters</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>General</value>
@@ -2142,7 +2220,7 @@
<value>Resource Download</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>Client</value>
<value>Full Package</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Pre-download</value>
@@ -2192,6 +2270,12 @@
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>Take advantage of displays that support HDR for brighter, more vivid, and more detailed pictures</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>Enter your HoYoLab UID</value>
</data>
@@ -2205,7 +2289,7 @@
<value>Open Screenshots Folder</value>
</data>
<data name="ViewPageResetGamePathAction" xml:space="preserve">
<value>Select game path</value>
<value>Select Game Path</value>
</data>
<data name="ViewPageSettingAboutHeader" xml:space="preserve">
<value>About Snap Hutao</value>
@@ -2409,7 +2493,7 @@
<value>After a full reading of the Genshin Impact and Snap Hutao user agreements, I choose to enable「Game Launcher - Advanced Features」.</value>
</data>
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
<value>Enable Advanced Features</value>
<value>Advanced Features</value>
</data>
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
<value>Change Auto Click Shortcut</value>
@@ -2520,7 +2604,7 @@
<value>Quotes</value>
</data>
<data name="ViewPageWiKiAvatarSpecialFoodTitle" xml:space="preserve">
<value>Special Food</value>
<value>Special Dish</value>
</data>
<data name="ViewPageWiKiAvatarStoriesHeader" xml:space="preserve">
<value>Stories</value>
@@ -2564,9 +2648,18 @@
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
<value>Debug Console</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
<value>Cache file are downloaded for server conversion in Game Launcher</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
<value>Delete Server Conversion Cache</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>Open Folder</value>
</data>
<data name="ViewSettingHeader" xml:space="preserve">
<value>Settings</value>
</data>
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
<value>Character Appearance Rate = Character Appearance in this Floor (only count for 1 if repeated) / Total Number of Abyss Record of this Floor</value>
</data>
@@ -2661,7 +2754,7 @@
<value>Web Login</value>
</data>
<data name="ViewUserCookieOperationLoginQRCodeAction" xml:space="preserve">
<value>Login via scanning QR code</value>
<value>Login via QR code</value>
</data>
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
<value>Input Manually</value>
@@ -2747,6 +2840,9 @@
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
<value>End in {0} hours</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>Failed to open clipboard</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>Copied to clipboard</value>
</data>
@@ -2811,10 +2907,10 @@
<value>{0} day</value>
</data>
<data name="WebDailyNoteResinRecoveryCompleted" xml:space="preserve">
<value>Original Resin is full</value>
<value>Original Resin is Full</value>
</data>
<data name="WebDailyNoteResinRecoveryFormat" xml:space="preserve">
<value>Will be replenished in {0} {1:HH:mm}</value>
<value>Will be Replenished in {0} {1:HH:mm}</value>
</data>
<data name="WebDailyNoteTransformerAppend" xml:space="preserve">
<value>Ready for use again after</value>
@@ -2847,7 +2943,7 @@
<value>{0} Seconds</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>Verification failed. Please check MiHoYo BBS - My Character - Realtime Notes manually</value>
<value>Verification failed. Please check MiHoYo BBS - Toolbox - Realtime Notes manually</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>Wrong UID format</value>
@@ -2916,7 +3012,7 @@
<value>Snap Hutao server is under maintenance</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>Verification failed. Please verify manually or check MiHoYo BBS - My Characters page</value>
<value>Verification failed. Please verify manually or check MiHoYo BBS - Battle Chronicle page</value>
</data>
<data name="WebResponseFormat" xml:space="preserve">
<value>Return Code: {0} | Message: {1}</value>
@@ -2927,4 +3023,7 @@
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
<value>[{1}] network request exception in [{0}] please try again later</value>
</data>
</root>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>Monitor ID</value>
</data>
</root>

View File

@@ -121,7 +121,7 @@
<value>Snap Hutao Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>Snap Hutao Dev {0} [Administrator]</value>
<value>胡桃Dev {0} [管理员]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>Snap Hutao {0} [Administrator]</value>
@@ -738,10 +738,10 @@
<value>Kalkulator Avatar: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>Karakter Saya: N/A</value>
<value>Battle Chronicle: Belum Disegarkan</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>Karakter Saya: {0:MM-dd HH:mm}</value>
<value>Battle Chronicle: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>Enka: N/A</value>
@@ -870,11 +870,23 @@
<value>Tidak ada izin menulis dalam sistem berkas, tidak dapat memulai konversi server.</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>Mencari Informasi Sumber Daya Game</value>
<value>Unduh indeks sumber daya game</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>Gagal melakukan operasi pada file game: {0}</value>
</data>
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
<value>Gagal membuka batas frame rate</value>
</data>
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
<value>Proses game sedang berjalan</value>
</data>
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
<value>Mohon pilih path game</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>Gagal mengunduh indeks sumber daya game: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>Proses game ditutup</value>
</data>
@@ -1175,6 +1187,12 @@
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>Catatan Realtime Webhook URL</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制后需要使用其他工具恢复限制</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>是否解除 Loopback 限制</value>
</data>
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
<value>Mengimpor riwayat wish</value>
</data>
@@ -1271,6 +1289,12 @@
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
<value>Pindai kode QR dengan Aplikasi MiHoYo BBS</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>Untuk menghindari mengaktifkan secara keliru, harap masukkan &lt;b&gt;nama judul&lt;/b&gt; dari fitur yang Anda aktifkan</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>Anda Mengaktifkan Fitur Berbahaya</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>Tindakan ini tidak dapat dibatalkan, dan semua status login pengguna akan hilang.</value>
</data>
@@ -1292,6 +1316,9 @@
<data name="ViewDialogUserTitle" xml:space="preserve">
<value>Setel Cookie</value>
</data>
<data name="ViewFeedbackHeader" xml:space="preserve">
<value>Pusat Umpan Balik</value>
</data>
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>Riwayat Wish</value>
</data>
@@ -1430,6 +1457,9 @@
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
<value>Akun HoYoLab tidak mendukung verifikasi Catatan Realtime.</value>
</data>
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
<value>Gagal mengubah tugas terjadwal</value>
</data>
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
<value>30 min | 3.75 Resin</value>
</data>
@@ -1445,9 +1475,6 @@
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
<value>8 min | 1 Resin</value>
</data>
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
<value>Gagal menambahkan Tugas Terjadwal, silakan coba lagi dalam mode administrator.</value>
</data>
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
<value>Mengambil data Catatan Realtime, mohon tunggu.</value>
</data>
@@ -1541,6 +1568,9 @@
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>Konversi server gagal</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>Identifikasi Monitor</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>Tidak dapat membaca file konfigurasi game: {0}, file mungkin tidak ada atau kurang izin pengguna</value>
</data>
@@ -1574,6 +1604,12 @@
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>Gagal membuat shortcut di desktop</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
<value>Anda akan diminta mengunduh file yang diperlukan, Yakin ingin menghapus?</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
<value>Menghapus Cache Server Konfersi</value>
</data>
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
<value>Ruang disk yang digunakan: {0}</value>
</data>
@@ -1698,7 +1734,7 @@
<value>Sinkronisasi Data Talenta Karakter</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>Sinkronkan dari Karakter Saya MiHoYo BBS</value>
<value>Sinkronisasi dari MiHoYo BBS Battle Chronicle</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>Sinkronkan sebagian besar data kecuali talent karakter.</value>
@@ -1823,6 +1859,48 @@
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
<value>Verifikasi Pengguna dan Role Saat Ini</value>
</data>
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
<value>Cari pertanyaan dan saran</value>
</data>
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
<value>Informasi Dasar</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>Tautan Berguna</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>当前代理</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>无代理</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>已解除</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>解除 Loopback 限制</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>Tetap berhubungan dengan kami</value>
</data>
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
<value>Dokumen Fitur</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>Kami selalu memberikan prioritas pada masalah yang dilaporkan di GitHub.</value>
</data>
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
<value>Roadmap Pengembangan</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>Tidak ada hasil</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>Pemantau Ketersediaan Layanan Snap Hutao</value>
</data>
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
<value>Servis Snap Hutao</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>Segarkan Seutuhnya</value>
</data>
@@ -2142,7 +2220,7 @@
<value>Pengunduhan Sumber Daya</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>Klien</value>
<value>完整包</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Prapengunduhan</value>
@@ -2192,6 +2270,12 @@
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
<value>Aktifkan</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>Manfaatkan tampilan yang mendukung rentang dinamis tinggi untuk gambar yang lebih terang, lebih jelas, dan lebih detail</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>Masukkan UID HoYoLab Anda</value>
</data>
@@ -2564,9 +2648,18 @@
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
<value>Konsol debug</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
<value>File Cache diunduh untuk Server Konfersi pada Game Launcher</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
<value>Menghapus Cache Server Konfersi</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>Buka berkas</value>
</data>
<data name="ViewSettingHeader" xml:space="preserve">
<value>Pengaturan</value>
</data>
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
<value>Rasio Penampilan Karakter = Penampilan Karakter di Lantai Ini (hanya dihitung sekali jika berulang) / Total Jumlah Rekor Abyss di Lantai Ini</value>
</data>
@@ -2747,6 +2840,9 @@
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
<value>Selesai {0} jam</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>Gagal membuka clipboard</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>Disalin ke clipboard</value>
</data>
@@ -2847,7 +2943,7 @@
<value>{0} detik</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>Verifikasi gagal. Silakan periksa MiHoYo BBS - Karakter Saya - Catatan Realtime secara manual.</value>
<value>Verifikasi gagal. Harap periksa MiHoYo BBS - Toolbox - Catatan Real-time secara manual.</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>Format UID Salah</value>
@@ -2916,7 +3012,7 @@
<value>Server Snap Hutao sedang dalam perbaikan</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>Verifikasi gagal. Harap verifikasi secara manual atau periksa halaman Karakter Saya di MiHoYo BBS.</value>
<value>Verifikasi gagal. Harap verifikasi secara manual atau periksa halaman MiHoYo BBS - Battle Chronicle.</value>
</data>
<data name="WebResponseFormat" xml:space="preserve">
<value>Kode Kembali: {0} | Pesan: {1}</value>
@@ -2927,4 +3023,7 @@
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
<value>[{1}] pengecualian permintaan jaringan di [{0}], harap coba lagi nanti</value>
</data>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>ID Monitor</value>
</data>
</root>

View File

@@ -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>
@@ -738,10 +738,10 @@
<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>
<value>原神の記録:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>キャラクターラインナップ:未更新</value>
@@ -870,11 +870,23 @@
<value>ファイル書き込みの権限が無いため、サーバー変換機能を使用できません</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>ゲームリソース情報を確認</value>
<value>ゲームリソースのインデックスをダウンロード</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>ゲームファイルの操作に失敗しました。: {0}</value>
</data>
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
<value>FPS上限の解放に失敗しました</value>
</data>
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
<value>ゲームは実行中です</value>
</data>
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
<value>ゲームのパスを選択してください</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>ゲームリソースのインデックスのダウンロードに失敗しました: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>ゲームプロセスが終了しました</value>
</data>
@@ -1175,6 +1187,12 @@
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>リアルタイムートのWebhook URL</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制后需要使用其他工具恢复限制</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>是否解除 Loopback 限制</value>
</data>
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
<value>祈願記録をインポート</value>
</data>
@@ -1271,6 +1289,12 @@
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
<value>MiHoYo BBS を使用して QR コードをスキャンします</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>謝って有効にしないようにするためには、有効にする機能の&lt;b&gt;タイトル名&lt;/b&gt;を入力してください</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>危険な機能を有効にしています</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>この操作は取り消せません。すべてのユーザーのログイン状態が解除されます。</value>
</data>
@@ -1292,6 +1316,9 @@
<data name="ViewDialogUserTitle" xml:space="preserve">
<value>クッキーを設定</value>
</data>
<data name="ViewFeedbackHeader" xml:space="preserve">
<value>フィードバック センター</value>
</data>
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈願履歴</value>
</data>
@@ -1430,6 +1457,9 @@
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
<value>HoYoLabユーザーのリアルタイムートはサポートしていません。</value>
</data>
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
<value>スケジュールされたタスクの変更に失敗しました</value>
</data>
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
<value>30 分 | 3.75樹脂</value>
</data>
@@ -1445,9 +1475,6 @@
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
<value>8 分 | 1樹脂</value>
</data>
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
<value>タスク スケジューラーへの追加に失敗しました。管理者モードで再度お試しください。</value>
</data>
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
<value>リアルタイムノートの情報を取得中、しばらくお待ちください</value>
</data>
@@ -1541,6 +1568,9 @@
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>サーバーの切り替えができませんでした</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>モニターの識別</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが無いか、権限が不足している可能性があります。</value>
</data>
@@ -1574,6 +1604,12 @@
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>デスクトップへのショートカット作成に失敗しました</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
<value>必要なファイルを再度ダウンロードする必要があります、削除してもよろしいですか?</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
<value>サーバー変換用キャッシュファイルの削除</value>
</data>
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
<value>使用済みディスク容量: {0}</value>
</data>
@@ -1698,7 +1734,7 @@
<value>キャラクターの天賦情報を同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>MiHoYo BBSから所持キャラを同期</value>
<value>MiHoYo BBSから原神の記録を同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>キャラ天賦以外の情報を概ね同期</value>
@@ -1823,6 +1859,48 @@
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
<value>現在のユーザーとUIDを確認する</value>
</data>
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
<value>質問や提案を検索</value>
</data>
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
<value>基本情報</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>よく使われるリンク</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>当前代理</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>无代理</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>已解除</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>解除 Loopback 限制</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>引き続き連絡をしてください</value>
</data>
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
<value>機能ガイド</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>Githubでは報告された問題を常に優先しています</value>
</data>
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
<value>開発ロードマップ</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>検索結果はありません</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>胡桃のサービス状態</value>
</data>
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
<value>胡桃サービス</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>すべて更新</value>
</data>
@@ -2142,7 +2220,7 @@
<value>リソースダウンロード</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>クライアント</value>
<value>完整包</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>事前ダウンロード</value>
@@ -2192,6 +2270,12 @@
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
<value>有効</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>HDRをサポートするディスプレイを活用して、より明るく鮮やかなグラフィックを実現します。</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>HoYoLab UIDを入力してください</value>
</data>
@@ -2205,7 +2289,7 @@
<value>スクリーンショットフォルダを開く</value>
</data>
<data name="ViewPageResetGamePathAction" xml:space="preserve">
<value>ゲームフォルダを選択してください。</value>
<value>ゲームフォルダを選択</value>
</data>
<data name="ViewPageSettingAboutHeader" xml:space="preserve">
<value>胡桃について</value>
@@ -2564,9 +2648,18 @@
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
<value>デバッグコンソール</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
<value>サーバー変換を使用すると、対応するサーバーのゲームクライアントのキャッシュファイルが生成されます</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
<value>サーバー変換のキャッシュを削除</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>フォルダを開く</value>
</data>
<data name="ViewSettingHeader" xml:space="preserve">
<value>設定</value>
</data>
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
<value>キャラクター出場率 = この階層における出場回数(最初の一回のみカウント) / この階層のアップロード総数</value>
</data>
@@ -2747,6 +2840,9 @@
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
<value>{0} 時間後に終了</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>クリップボードを開けませんでした</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>クリップボードにコピーしました。</value>
</data>
@@ -2847,7 +2943,7 @@
<value>{0} 秒</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認し、認証を行ってください</value>
<value>認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認してください</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>UIDは正しくありません</value>
@@ -2916,7 +3012,7 @@
<value>胡桃サーバがメンテナンス中です</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>認証に失敗しました。 手動で認証するか、MiHoYo BBS - 戦績 を確認してください。</value>
<value>認証に失敗しました。 手動で認証するか、MiHoYo BBS - 戦績 - マイ キャラクター を確認してください。</value>
</data>
<data name="WebResponseFormat" xml:space="preserve">
<value>リターンコード:{0} | メッセージ:{1}</value>
@@ -2927,4 +3023,7 @@
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
<value>[{0}] の[{1}] のリクエストにエラーが発生、時間をおいてから試してください</value>
</data>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>モニターID</value>
</data>
</root>

View File

@@ -738,10 +738,10 @@
<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>
<value>原神战绩{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>角色橱窗:尚未刷新</value>
@@ -870,11 +870,23 @@
<value>文件系统权限不足,无法转换服务器</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>게임 리소스 정보 조회</value>
<value>下载游戏资源索引</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>게임 파일 작업 실패:{0}</value>
</data>
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
<value>解锁帧率上限失败</value>
</data>
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
<value>游戏进程运行中</value>
</data>
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
<value>请选择游戏路径</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>下载游戏资源索引失败: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>游戏进程已退出</value>
</data>
@@ -1175,6 +1187,12 @@
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>实时便笺 Webhook Url</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制后需要使用其他工具恢复限制</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>是否解除 Loopback 限制</value>
</data>
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
<value>기원 기록 가져오기</value>
</data>
@@ -1271,6 +1289,12 @@
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
<value>使用米游社扫描二维码</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>为防止你在无意间启用,请输入正在启用的功能开关的&lt;b&gt;标题名称&lt;/b&gt;</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>你正在启用一个危险功能</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>이 작업은 되돌릴 수 없으며, 모든 사용자 로그인 상태가 해제됩니다</value>
</data>
@@ -1292,6 +1316,9 @@
<data name="ViewDialogUserTitle" xml:space="preserve">
<value>쿠키 설정</value>
</data>
<data name="ViewFeedbackHeader" xml:space="preserve">
<value>反馈中心</value>
</data>
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>기원 기록</value>
</data>
@@ -1430,6 +1457,9 @@
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
<value>HoYoLab 계정은 실시간 메모 확인 기능을 지원하지 않습니다</value>
</data>
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
<value>예약된 작업을 수정하지 못했습니다</value>
</data>
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
<value>30분 | 3.75 레진</value>
</data>
@@ -1445,9 +1475,6 @@
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
<value>8분 | 1 레진</value>
</data>
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
<value>작업 등록에 실패했습니다. 관리자 모드로 실행하세요</value>
</data>
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
<value>正在获取实时便笺信息,请稍候</value>
</data>
@@ -1541,6 +1568,9 @@
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>서버 변경 실패</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>识别显示器</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>无法读取游戏配置文件: {0},可能是文件不存在或权限不足</value>
</data>
@@ -1574,6 +1604,12 @@
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>创建桌面快捷方式失败</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
<value>后续转换会重新下载所需的文件,确定要删除吗?</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
<value>删除转换服务器游戏客户端缓存</value>
</data>
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
<value>已使用磁盘空间:{0}</value>
</data>
@@ -1698,7 +1734,7 @@
<value>캐릭터 특성 정보 동기화</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>HoYoLAB에서 내 캐릭터 동기화</value>
<value>从米游社原神战绩同步</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>캐릭터 특성을 제외한 대부분의 정보 동기화</value>
@@ -1823,6 +1859,48 @@
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
<value>현재 사용자와 캐릭터 확인</value>
</data>
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
<value>搜索问题与建议</value>
</data>
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
<value>基本信息</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>常用链接</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>当前代理</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>无代理</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>已解除</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>解除 Loopback 限制</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>与我们密切联系</value>
</data>
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
<value>功能指南</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>我们总是优先处理 GitHub 上的问题</value>
</data>
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
<value>开发路线规划</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>暂无搜索结果</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>胡桃服务可用性监控</value>
</data>
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
<value>胡桃服务</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>전체 동기화</value>
</data>
@@ -2142,7 +2220,7 @@
<value>리소스 다운로드</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>클라이언트</value>
<value>完整包</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>사전 다운로드</value>
@@ -2192,6 +2270,12 @@
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
<value>활성화</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>充分利用支持高动态范围的显示器获得更亮、更生动、更精细的画面</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>HoYoLab Uid를 입력하세요</value>
</data>
@@ -2564,9 +2648,18 @@
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
<value>调试控制台</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
<value>在启动游戏中转换服务器后会产生对应的游戏客户端文件用作缓存</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
<value>删除转换服务器缓存</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>打开文件夹</value>
</data>
<data name="ViewSettingHeader" xml:space="preserve">
<value>设置</value>
</data>
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
<value>角色出场率 = 本层上阵该角色次数(层内重复出现只记一次)/ 深渊记录总数</value>
</data>
@@ -2747,6 +2840,9 @@
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
<value>{0}시간 후 종료</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>打开剪贴板失败</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已复制到剪贴板</value>
</data>
@@ -2847,7 +2943,7 @@
<value>{0}초</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>인증에 실패했습니다. 수동으로 인증하거나 'HoYoLAB-전적-실시간 메모' 페이지에서 확인하시기 바랍니다.</value>
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩-实时便笺」页面查看</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>错误的 UID 格式</value>
@@ -2916,7 +3012,7 @@
<value>胡桃服务维护中</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米游社-我的角色」页面查看</value>
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩」页面查看</value>
</data>
<data name="WebResponseFormat" xml:space="preserve">
<value>상태:{0} | 정보:{1}</value>
@@ -2927,4 +3023,7 @@
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
<value>[{0}] 中的 [{1}] 网络请求异常,请稍后再试</value>
</data>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>显示器编号</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

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