Compare commits

...

81 Commits

Author SHA1 Message Date
qhy040404
974afa86ee impl #1334 2024-02-02 11:06:08 +08:00
Lightczx
7baf125f88 Impl IReorderable 2024-02-02 10:03:46 +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
275 changed files with 14601 additions and 2648 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,109 +0,0 @@
// ADVAPI32
RegCloseKey
RegOpenKeyExW
RegNotifyChangeKeyValue
REG_NOTIFY_FILTER
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
HKEY_CURRENT_CONFIG
// 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
GetWindowLongPtrW
GetWindowPlacement
GetWindowThreadProcessId
ReleaseDC
RegisterHotKey
SendInput
SetForegroundWindow
SetWindowLongPtrW
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_ERASEBKGND
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
WINDOW_EX_STYLE
// 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,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
</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

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

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

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

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

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>

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
@@ -11,13 +12,12 @@ namespace Snap.Hutao.Core.Database;
internal sealed class ObservableReorderableDbCollection<T> : ObservableCollection<T>
where T : class, IReorderable
{
private readonly DbContext dbContext;
private bool previousChangeIsRemoved;
private readonly IServiceProvider serviceProvider;
public ObservableReorderableDbCollection(List<T> items, DbContext dbContext)
public ObservableReorderableDbCollection(List<T> items, IServiceProvider serviceProvider)
: base(AdjustIndex(items))
{
this.dbContext = dbContext;
this.serviceProvider = serviceProvider;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
@@ -27,16 +27,8 @@ internal sealed class ObservableReorderableDbCollection<T> : ObservableCollectio
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
previousChangeIsRemoved = true;
break;
case NotifyCollectionChangedAction.Add:
if (!previousChangeIsRemoved)
{
return;
}
OnReorder();
previousChangeIsRemoved = false;
break;
}
}
@@ -57,10 +49,15 @@ internal sealed class ObservableReorderableDbCollection<T> : ObservableCollectio
{
AdjustIndex((List<T>)Items);
DbSet<T> dbSet = dbContext.Set<T>();
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
using (IServiceScope scope = serviceProvider.CreateScope())
{
dbSet.UpdateAndSave(item);
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<T> dbSet = appDbContext.Set<T>();
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
{
dbSet.UpdateAndSave(item);
}
}
}
}

View File

@@ -2,7 +2,8 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.IO.Http.DynamicProxy;
using Microsoft.Extensions.Http;
using Snap.Hutao.Core.IO.Http.Proxy;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service;
using System.Globalization;
@@ -33,7 +34,7 @@ internal static class DependencyInjection
.AddJsonOptions()
.AddDatabase()
.AddInjections()
.AddHttpClients()
.AddAllHttpClients()
// Discrete services
.AddSingleton<IMessenger, WeakReferenceMessenger>()
@@ -43,7 +44,6 @@ internal static class DependencyInjection
serviceProvider.InitializeConsoleWindow();
serviceProvider.InitializeCulture();
serviceProvider.InitializedDynamicHttpProxy();
return serviceProvider;
}
@@ -70,9 +70,4 @@ internal static class DependencyInjection
{
_ = serviceProvider.GetRequiredService<ConsoleWindowLifeTime>();
}
private static void InitializedDynamicHttpProxy(this IServiceProvider serviceProvider)
{
HttpClient.DefaultProxy = serviceProvider.GetRequiredService<DynamicHttpProxy>();
}
}

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

@@ -1,49 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// 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.DynamicProxy;
namespace Snap.Hutao.Core.IO.Http.Proxy;
[Injection(InjectAs.Singleton)]
internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
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 MethodInfo ConstructSystemProxyMethod;
private static readonly Lazy<MethodInfo> LazyConstructSystemProxyMethod = new(GetConstructSystemProxyMethod);
private readonly IServiceProvider serviceProvider;
private readonly RegistryWatcher watcher;
private IWebProxy innerProxy = default!;
static DynamicHttpProxy()
public DynamicHttpProxy(IServiceProvider serviceProvider)
{
Type? systemProxyInfoType = typeof(System.Net.Http.SocketsHttpHandler).Assembly.GetType("System.Net.Http.SystemProxyInfo");
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
this.serviceProvider = serviceProvider;
UpdateInnerProxy();
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
ConstructSystemProxyMethod = constructSystemProxyMethod;
}
public DynamicHttpProxy()
{
UpdateProxy();
watcher = new(ProxySettingPath, UpdateProxy);
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
watcher.Start();
}
/// <inheritdoc/>
public ICredentials? Credentials
public string CurrentProxyUri
{
get => InnerProxy.Credentials;
set => InnerProxy.Credentials = value;
get
{
Uri? proxyUri = GetProxy("https://hut.ao".ToUri());
return proxyUri is null
? SH.ViewPageFeedbackCurrentProxyNoProxyDescription
: proxyUri.AbsoluteUri;
}
}
private IWebProxy InnerProxy
public IWebProxy InnerProxy
{
get => innerProxy;
@@ -60,13 +57,10 @@ internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
}
}
[MemberNotNull(nameof(innerProxy))]
public void UpdateProxy()
public ICredentials? Credentials
{
IWebProxy? proxy = ConstructSystemProxyMethod.Invoke(default, default) as IWebProxy;
ArgumentNullException.ThrowIfNull(proxy);
InnerProxy = proxy;
get => InnerProxy.Credentials;
set => InnerProxy.Credentials = value;
}
public Uri? GetProxy(Uri destination)
@@ -84,4 +78,33 @@ internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
(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

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

@@ -62,6 +62,4 @@ internal sealed class TransparentBackdrop : SystemBackdrop, IDisposable, IBackdr
{
disconnectedTarget.SystemBackdrop = null;
}
}
internal interface IBackdropNeedEraseBackground;
}

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,14 +9,15 @@ using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service;
using System.Diagnostics;
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;
@@ -105,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));
@@ -195,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,11 +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 Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT.Interop;
using static Windows.Win32.PInvoke;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Core.Windowing;
@@ -24,8 +24,8 @@ internal static class WindowExtension
public static void SetLayeredWindow(this Window window)
{
HWND hwnd = (HWND)WindowNative.GetWindowHandle(window);
nint style = GetWindowLongPtr(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED;
SetWindowLongPtr(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style);
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

@@ -4,10 +4,12 @@
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;
@@ -26,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)
{
@@ -57,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)
{
@@ -76,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!;
}
}
@@ -94,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;
@@ -116,7 +118,7 @@ internal sealed class WindowSubclass : IDisposable
{
if (window.SystemBackdrop is IBackdropNeedEraseBackground)
{
return (LRESULT)(int)(BOOL)true;
return (LRESULT)(int)BOOL.TRUE;
}
break;

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Text;
@@ -113,6 +114,13 @@ internal static partial class EnumerableExtension
return new ObservableCollection<T>(source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ObservableReorderableDbCollection<T> ToObservableReorderableDbCollection<T>(this IEnumerable<T> source, IServiceProvider serviceProvider)
where T : class, IReorderable
{
return new ObservableReorderableDbCollection<T>([.. source], serviceProvider);
}
/// <summary>
/// Concatenates each element from the collection into single string.
/// </summary>

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

@@ -0,0 +1,624 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Snap.Hutao.Model.Entity.Database;
#nullable disable
namespace Snap.Hutao.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20240202015857_ImplReorderableOnUserAndGameAccount")]
partial class ImplReorderableOnUserAndGameAccount
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<uint>("Current")
.HasColumnType("INTEGER");
b.Property<uint>("Id")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("achievements");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("achievement_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CalculatorRefreshTime")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("GameRecordRefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Info")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ShowcaseRefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("avatar_infos");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Id")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("AvatarLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("AvatarLevelTo")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<uint>("SkillALevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillALevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("WeaponLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("WeaponLevelTo")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("EntryId")
.IsUnique();
b.ToTable("cultivate_entry_level_informations");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<bool>("IsFinished")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachedUid")
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DailyNote")
.HasColumnType("TEXT");
b.Property<bool>("DailyTaskNotify")
.HasColumnType("INTEGER");
b.Property<bool>("DailyTaskNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotify")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("HomeCoinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("HomeCoinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RefreshTime")
.HasColumnType("TEXT");
b.Property<bool>("ResinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("ResinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotify")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("UserId");
b.ToTable("daily_notes");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("gacha_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<int>("GachaType")
.HasColumnType("INTEGER");
b.Property<long>("Id")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("QueryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.ToTable("game_accounts");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AppendPropIdList")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<int>("MainPropId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("PromoteLevel")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ExpireTime")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("object_cache");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("settings");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("ScheduleId")
.HasColumnType("INTEGER");
b.Property<string>("SpiralAbyss")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("spiral_abysses");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Aid")
.HasColumnType("TEXT");
b.Property<string>("CookieToken")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
.HasColumnType("TEXT");
b.Property<string>("Fingerprint")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<bool>("IsOversea")
.HasColumnType("INTEGER");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("LToken")
.HasColumnType("TEXT")
.HasColumnName("Ltoken");
b.Property<string>("Mid")
.HasColumnType("TEXT");
b.Property<string>("SToken")
.HasColumnType("TEXT")
.HasColumnName("Stoken");
b.HasKey("InnerId");
b.ToTable("users");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
.WithMany()
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithOne("LevelInformation")
.HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithMany()
.HasForeignKey("EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
.WithMany()
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Navigation("LevelInformation");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,60 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class ImplReorderableOnUserAndGameAccount : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations");
migrationBuilder.AddColumn<int>(
name: "Index",
table: "users",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "Index",
table: "game_accounts",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations",
column: "EntryId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations");
migrationBuilder.DropColumn(
name: "Index",
table: "users");
migrationBuilder.DropColumn(
name: "Index",
table: "game_accounts");
migrationBuilder.CreateIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations",
column: "EntryId");
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
@@ -42,7 +42,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("achievements", (string)null);
b.ToTable("achievements");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
@@ -60,7 +60,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("achievement_archives", (string)null);
b.ToTable("achievement_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
@@ -88,7 +88,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("avatar_infos", (string)null);
b.ToTable("avatar_infos");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
@@ -110,7 +110,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries", (string)null);
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
@@ -154,9 +154,10 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.HasIndex("EntryId");
b.HasIndex("EntryId")
.IsUnique();
b.ToTable("cultivate_entry_level_informations", (string)null);
b.ToTable("cultivate_entry_level_informations");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
@@ -181,7 +182,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("EntryId");
b.ToTable("cultivate_items", (string)null);
b.ToTable("cultivate_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
@@ -202,7 +203,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("cultivate_projects", (string)null);
b.ToTable("cultivate_projects");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
@@ -258,7 +259,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("UserId");
b.ToTable("daily_notes", (string)null);
b.ToTable("daily_notes");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
@@ -276,7 +277,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("gacha_archives", (string)null);
b.ToTable("gacha_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
@@ -307,7 +308,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("gacha_items", (string)null);
b.ToTable("gacha_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
@@ -319,6 +320,9 @@ namespace Snap.Hutao.Migrations
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
@@ -332,7 +336,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("game_accounts", (string)null);
b.ToTable("game_accounts");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
@@ -354,7 +358,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_items", (string)null);
b.ToTable("inventory_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
@@ -383,7 +387,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries", (string)null);
b.ToTable("inventory_reliquaries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
@@ -408,7 +412,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons", (string)null);
b.ToTable("inventory_weapons");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
@@ -424,7 +428,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("object_cache", (string)null);
b.ToTable("object_cache");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
@@ -437,7 +441,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("settings", (string)null);
b.ToTable("settings");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
@@ -459,7 +463,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("spiral_abysses", (string)null);
b.ToTable("spiral_abysses");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
@@ -483,6 +487,9 @@ namespace Snap.Hutao.Migrations
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<bool>("IsOversea")
.HasColumnType("INTEGER");
@@ -502,7 +509,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("users", (string)null);
b.ToTable("users");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
@@ -530,8 +537,8 @@ namespace Snap.Hutao.Migrations
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithMany()
.HasForeignKey("EntryId")
.WithOne("LevelInformation")
.HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -603,6 +610,11 @@ namespace Snap.Hutao.Migrations
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Navigation("LevelInformation");
});
#pragma warning restore 612, 618
}
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Primitive;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -14,7 +15,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, IReorderable, IMappingFrom<GameAccount, string, string, SchemeType>
{
/// <summary>
/// 内部Id
@@ -44,6 +45,8 @@ internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount,
/// </summary>
public string MihoyoSDK { get; set; } = default!;
public int Index { get; set; }
public static GameAccount From(string name, string sdk, SchemeType type)
{
return new()

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("users")]
internal sealed class User : ISelectable, IMappingFrom<User, Cookie, bool>
internal sealed class User : ISelectable, IReorderable, IMappingFrom<User, Cookie, bool>
{
/// <summary>
/// 内部Id
@@ -69,6 +69,8 @@ internal sealed class User : ISelectable, IMappingFrom<User, Cookie, bool>
public DateTimeOffset CookieTokenLastUpdateTime { get; set; }
public int Index { get; set; }
/// <summary>
/// 创建一个新的用户
/// </summary>

View File

@@ -4,7 +4,6 @@
using Snap.Hutao.Core;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Service;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Model.InterChange.GachaLog;

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.
-->
@@ -738,10 +738,10 @@
<value>Avatar Calculator: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>My Character: Not Refreshed</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>
@@ -1170,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>
@@ -1187,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>
@@ -1451,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>
@@ -1466,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>
@@ -1512,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>
@@ -1728,7 +1734,7 @@
<value>Sync character talents data</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>Sync from MiHoYo BBS My Character</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>
@@ -1862,6 +1868,18 @@
<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>
@@ -2475,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>
@@ -2586,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>
@@ -2925,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>
@@ -2994,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>
@@ -3008,4 +3026,4 @@
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>Monitor ID</value>
</data>
</root>
</root>

View File

@@ -738,10 +738,10 @@
<value>Kalkulator Avatar: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>原神战绩:尚未刷新</value>
<value>Battle Chronicle: Belum Disegarkan</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>原神战绩:{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>
@@ -1187,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>
@@ -1284,7 +1290,7 @@
<value>Pindai kode QR dengan Aplikasi MiHoYo BBS</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>为防止你在无意间启用,请输入正在启用的功能开关的&lt;b&gt;标题名称&lt;/b&gt;</value>
<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>
@@ -1451,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>
@@ -1466,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>
@@ -1728,7 +1734,7 @@
<value>Sinkronisasi Data Talenta Karakter</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>从米游社原神战绩同步</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>
@@ -1862,6 +1868,18 @@
<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>
@@ -1869,13 +1887,13 @@
<value>Dokumen Fitur</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>我们总是优先处理 GitHub 上的问题</value>
<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>暂无搜索结果</value>
<value>Tidak ada hasil</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>Pemantau Ketersediaan Layanan Snap Hutao</value>
@@ -2202,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>
@@ -2925,7 +2943,7 @@
<value>{0} detik</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩-实时便笺」页面查看</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>
@@ -2994,7 +3012,7 @@
<value>Server Snap Hutao sedang dalam perbaikan</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩」页面查看</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>

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,22 +870,22 @@
<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>
<value>FPS上限の解放に失敗しました</value>
</data>
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
<value>游戏进程运行中</value>
<value>ゲームは実行中です</value>
</data>
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
<value>请选择游戏路径</value>
<value>ゲームのパスを選択してください</value>
</data>
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
<value>下载游戏资源索引失败: {0}</value>
<value>ゲームリソースのインデックスのダウンロードに失敗しました: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>ゲームプロセスが終了しました</value>
@@ -1187,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>
@@ -1284,10 +1290,10 @@
<value>MiHoYo BBS を使用して QR コードをスキャンします</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>为防止你在无意间启用,请输入正在启用的功能开关的&lt;b&gt;标题名称&lt;/b&gt;</value>
<value>謝って有効にしないようにするためには、有効にする機能の&lt;b&gt;タイトル名&lt;/b&gt;を入力してください</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>你正在启用一个危险功能</value>
<value>危険な機能を有効にしています</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>この操作は取り消せません。すべてのユーザーのログイン状態が解除されます。</value>
@@ -1311,7 +1317,7 @@
<value>クッキーを設定</value>
</data>
<data name="ViewFeedbackHeader" xml:space="preserve">
<value>反馈中心</value>
<value>フィードバック センター</value>
</data>
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈願履歴</value>
@@ -1451,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>
@@ -1466,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>
@@ -1563,7 +1569,7 @@
<value>サーバーの切り替えができませんでした</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>识别显示器</value>
<value>モニターの識別</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが無いか、権限が不足している可能性があります。</value>
@@ -1599,10 +1605,10 @@
<value>デスクトップへのショートカット作成に失敗しました</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
<value>后续转换会重新下载所需的文件,确定要删除吗?</value>
<value>必要なファイルを再度ダウンロードする必要があります、削除してもよろしいですか?</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
<value>删除转换服务器游戏客户端缓存</value>
<value>サーバー変換用キャッシュファイルの削除</value>
</data>
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
<value>使用済みディスク容量: {0}</value>
@@ -1728,7 +1734,7 @@
<value>キャラクターの天賦情報を同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>从米游社原神战绩同步</value>
<value>MiHoYo BBSから原神の記録を同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>キャラ天賦以外の情報を概ね同期</value>
@@ -1854,34 +1860,46 @@
<value>現在のユーザーとUIDを確認する</value>
</data>
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
<value>搜索问题与建议</value>
<value>質問や提案を検索</value>
</data>
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
<value>基本信息</value>
<value>基本情報</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>常用链接</value>
<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>
<value>引き続き連絡をしてください</value>
</data>
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
<value>功能指南</value>
<value>機能ガイド</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>我们总是优先处理 GitHub 上的问题</value>
<value>Githubでは報告された問題を常に優先しています</value>
</data>
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
<value>开发路线规划</value>
<value>開発ロードマップ</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>暂无搜索结果</value>
<value>検索結果はありません</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>胡桃服务可用性监控</value>
<value>胡桃のサービス状態</value>
</data>
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
<value>胡桃服务</value>
<value>胡桃サービス</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>すべて更新</value>
@@ -2202,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>
@@ -2271,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>
@@ -2631,16 +2649,16 @@
<value>デバッグコンソール</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
<value>在启动游戏中转换服务器后会产生对应的游戏客户端文件用作缓存</value>
<value>サーバー変換を使用すると、対応するサーバーのゲームクライアントのキャッシュファイルが生成されます</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
<value>删除转换服务器缓存</value>
<value>サーバー変換のキャッシュを削除</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>フォルダを開く</value>
</data>
<data name="ViewSettingHeader" xml:space="preserve">
<value>设置</value>
<value>設定</value>
</data>
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
<value>キャラクター出場率 = この階層における出場回数(最初の一回のみカウント) / この階層のアップロード総数</value>
@@ -2823,7 +2841,7 @@
<value>{0} 時間後に終了</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>打开剪贴板失败</value>
<value>クリップボードを開けませんでした</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>クリップボードにコピーしました。</value>
@@ -2925,7 +2943,7 @@
<value>{0} 秒</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩-实时便笺」页面查看</value>
<value>認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認してください</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>UIDは正しくありません</value>
@@ -2994,7 +3012,7 @@
<value>胡桃サーバがメンテナンス中です</value>
</data>
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩」页面查看</value>
<value>認証に失敗しました。 手動で認証するか、MiHoYo BBS - 戦績 - マイ キャラクター を確認してください。</value>
</data>
<data name="WebResponseFormat" xml:space="preserve">
<value>リターンコード:{0} | メッセージ:{1}</value>
@@ -3006,6 +3024,6 @@
<value>[{0}] の[{1}] のリクエストにエラーが発生、時間をおいてから試してください</value>
</data>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>显示器编号</value>
<value>モニターID</value>
</data>
</root>

View File

@@ -1187,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>
@@ -1451,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>
@@ -1466,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>
@@ -1862,6 +1868,18 @@
<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>
@@ -2202,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>

File diff suppressed because it is too large Load Diff

View File

@@ -198,6 +198,21 @@
<data name="LaunchGameTitle" xml:space="preserve">
<value>选择账号并启动</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>旅行者</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>精炼 {0}</value>
</data>
@@ -497,6 +512,9 @@
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>第四波:击败所有怪物,下一波才会出现</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 个</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>请更新角色橱窗数据</value>
</data>
@@ -1187,6 +1205,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>
@@ -1451,6 +1475,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>
@@ -1466,9 +1493,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>
@@ -1862,6 +1886,18 @@
<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>
@@ -1974,7 +2010,7 @@
<value>总览</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>统计</value>
<value>全球祈愿统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>武器</value>
@@ -2202,7 +2238,7 @@
<value>资源下载</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>客户端</value>
<value>完整包</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>预下载</value>

View File

@@ -738,10 +738,10 @@
<value>Калькулятор аватара: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>原神战绩:尚未刷新</value>
<value>Запись Genshin Impact: еще не обновлены</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>原神战绩{0:MM-dd HH:mm}</value>
<value>Боевые достижения в Genshin Impact{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>Enka: N/A</value>
@@ -783,16 +783,16 @@
<value>Realm Currency</value>
</data>
<data name="ServiceDailyNoteNotifierHomeCoinCurrent" xml:space="preserve">
<value>当前洞天宝钱:{0}</value>
<value>Текущий баланс сокровищ в Дунтянь: {0}</value>
</data>
<data name="ServiceDailyNoteNotifierMultiValueReached" xml:space="preserve">
<value>多个提醒项达到设定值</value>
<value>Несколько напоминаний достигли установленного значения</value>
</data>
<data name="ServiceDailyNoteNotifierResin" xml:space="preserve">
<value>原粹树脂</value>
<value>Оригинальная смола</value>
</data>
<data name="ServiceDailyNoteNotifierResinCurrent" xml:space="preserve">
<value>当前原粹树脂{0}</value>
<value>Текущая оригинальная смола{0}</value>
</data>
<data name="ServiceDailyNoteNotifierTitle" xml:space="preserve">
<value>实时便笺提醒</value>
@@ -801,7 +801,7 @@
<value>参量质变仪</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerAdaptiveHint" xml:space="preserve">
<value>准备完成</value>
<value>Готово</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量质变仪已准备完成</value>
@@ -810,7 +810,7 @@
<value>正在提瓦特大陆中探索</value>
</data>
<data name="ServiceDiscordGameLaunchedBy" xml:space="preserve">
<value> {0} 启动</value>
<value>Started by {0}</value>
</data>
<data name="ServiceGachaLogArchiveCollectionUserdataCorruptedMessage" xml:space="preserve">
<value>无法获取祈愿记录:{0}</value>
@@ -852,16 +852,16 @@
<value>提供的 Url 无效</value>
</data>
<data name="ServiceGachaLogUrlProviderStokenUnsupported" xml:space="preserve">
<value>HoYoLab 账号不支持使用 SToken 刷新祈愿记录</value>
<value>SToken refresh не поддерживает учетную запись HoYoLab</value>
</data>
<data name="ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale" xml:space="preserve">
<value>Url 中的语言:{0} 与胡桃的语言:{1} 不匹配,请切换到对应语言重试</value>
<value>Язык в URL: {0} не соответствует языку Walnut: {1}, пожалуйста, переключитесь на соответствующий язык и повторите попытку</value>
</data>
<data name="ServiceGachaStatisticsFactoryItemIdInvalid" xml:space="preserve">
<value>不支持的 Item Id{0}</value>
<value>Неподдерживаемый Item ID: {0}</value>
</data>
<data name="ServiceGachaUIGFImportLanguageNotMatch" xml:space="preserve">
<value>UIGF 文件的语言:{0} 与胡桃的语言:{1} 不匹配,请切换到对应语言重试</value>
<value>Язык файла UIGF: {0} не соответствует языку Snap Hutao: {1}, пожалуйста, повторите попытку после переключения языка клиента на целевой язык файла UIGF</value>
</data>
<data name="ServiceGameDetectGameAccountMultiMatched" xml:space="preserve">
<value>存在多个匹配账号,请删除重复的账号</value>
@@ -894,7 +894,7 @@
<value>正在初始化游戏进程</value>
</data>
<data name="ServiceGameLaunchPhaseProcessStarted" xml:space="preserve">
<value>游戏进程已启动</value>
<value>Игра запускается...</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsFailed" xml:space="preserve">
<value>解锁帧率上限失败,正在结束游戏进程</value>
@@ -912,22 +912,22 @@
<value>选择游戏本体</value>
</data>
<data name="ServiceGameLocatorPickerFilterText" xml:space="preserve">
<value>游戏本体</value>
<value>Игровой клиент</value>
</data>
<data name="ServiceGameLocatorUnityLogFileNotFound" xml:space="preserve">
<value>找不到 Unity 日志文件</value>
</data>
<data name="ServiceGameLocatorUnityLogGamePathNotFound" xml:space="preserve">
<value>在 Unity 日志文件中找不到游戏路径</value>
<value>Путь к игре не найден в файле журнала Unity</value>
</data>
<data name="ServiceGamePackageConvertMoveFileBackupFormat" xml:space="preserve">
<value>备份{0}</value>
<value>Резервная копия{0}</value>
</data>
<data name="ServiceGamePackageConvertMoveFileRenameFormat" xml:space="preserve">
<value>重命名:{0} 到:{1}</value>
<value>Переименовать: {0} в:{1}</value>
</data>
<data name="ServiceGamePackageConvertMoveFileRestoreFormat" xml:space="preserve">
<value>替换:{0}</value>
<value>Изменить: {0}</value>
</data>
<data name="ServiceGamePackageRenameDataFolderFailed" xml:space="preserve">
<value>重命名数据文件夹名称失败</value>
@@ -969,148 +969,148 @@
<value>在读取游戏进程内存时遇到问题:无法读取到指定地址的有效值</value>
</data>
<data name="ServiceHutaoUserGachaLogExpiredAt" xml:space="preserve">
<value>祈愿记录上传服务有效期至\n{0:yyyy.MM.dd HH:mm:ss}</value>
<value>Запись молитв доступна до \n{0:yyyy.MM.dd HH:mm:ss}</value>
</data>
<data name="ServiceMetadataFileNotFound" xml:space="preserve">
<value>无法找到缓存的元数据文件</value>
</data>
<data name="ServiceMetadataHttpRequestFailed" xml:space="preserve">
<value>HTTP {0} | Error:{1}:元数据校验文件下载失败</value>
<value>HTTP {0} | Ошибка {1}: Не удалось загрузить файл проверки метаданных</value>
</data>
<data name="ServiceMetadataNotInitialized" xml:space="preserve">
<value>元数据服务尚未初始化,或初始化失败</value>
<value>Служба метаданных не была инициализирована или инициализация завершилась неудачно</value>
</data>
<data name="ServiceMetadataParseFailed" xml:space="preserve">
<value>元数据校验文件解析失败</value>
<value>Не удалось прочитать файл проверки метаданных.</value>
</data>
<data name="ServiceMetadataRequestFailed" xml:space="preserve">
<value>元数据校验文件下载失败</value>
<value>Не удалось загрузить файл проверки метаданных.</value>
</data>
<data name="ServiceMetadataVersionNotSupported" xml:space="preserve">
<value>你的胡桃版本过低,请尽快升级</value>
<value>Ваша версия Snap Hutao устарела. Пожалуйста, обновите приложение</value>
</data>
<data name="ServiceSignInClaimRewardFailedFormat" xml:space="preserve">
<value>签到失败,{0}</value>
<value>Ошибка регистрации, {0}</value>
</data>
<data name="ServiceSignInInfoRequestFailed" xml:space="preserve">
<value>获取签到次数失败</value>
<value>Не удалось получить количество дней регистрации</value>
</data>
<data name="ServiceSignInRewardListRequestFailed" xml:space="preserve">
<value>获取奖励列表失败</value>
<value>Не удалось получить список наград за регистрацию.</value>
</data>
<data name="ServiceSignInRiskVerificationFailed" xml:space="preserve">
<value>验证失败,请前往米游社原神签到页面自行领取奖励</value>
<value>Проверка не удалась. Пожалуйста, перейдите на страницу регистрации HoYoLab в Genshin Impact, чтобы вручную получить награду.</value>
</data>
<data name="ServiceSignInSuccessRewardFormat" xml:space="preserve">
<value>签到成功,{0}×{1}</value>
<value>Перемещение успешно, {0} × {1}.</value>
</data>
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
<value>不支持的 UIGF 版本</value>
<value>Неподдерживаемая версия UIGF.</value>
</data>
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
<value>发现新版本 {0}</value>
<value>Доступна новая версия {0}.</value>
</data>
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
<value>多个用户记录为选中状态</value>
<value>Зафиксирован выбор нескольких записей пользователей.</value>
</data>
<data name="ServiceUserCurrentUpdateAndSaveFailed" xml:space="preserve">
<value>用户 {0} 状态保存失败</value>
<value>Не удалось сохранить состояние пользователя {0}.</value>
</data>
<data name="ServiceUserProcessCookieNoMid" xml:space="preserve">
<value>输入的 Cookie 必须包含 Mid</value>
<value>Введенные Cookie должны включать Mid.</value>
</data>
<data name="ServiceUserProcessCookieNoSToken" xml:space="preserve">
<value>输入的 Cookie 必须包含 SToken</value>
<value>Введенные Cookie должны включать SToken.</value>
</data>
<data name="ServiceUserProcessCookieRequestUserInfoFailed" xml:space="preserve">
<value>输入的 Cookie 无法获取用户信息</value>
<value>Предоставленные Cookie не позволяют получить информацию о пользователе.</value>
</data>
<data name="ViewAchievementHeader" xml:space="preserve">
<value>成就管理</value>
<value>Достижения</value>
</data>
<data name="ViewAnnouncementHeader" xml:space="preserve">
<value>主页</value>
<value>Главная</value>
</data>
<data name="ViewAvatarPropertyHeader" xml:space="preserve">
<value>我的角色</value>
<value>Мои персонажи</value>
</data>
<data name="ViewAvatarPropertySyncDataButtonLabel" xml:space="preserve">
<value>同步角色信息</value>
<value>Синхронизация данных персонажа.</value>
</data>
<data name="ViewCardAchievementStatisticsTitle" xml:space="preserve">
<value>成就统计</value>
<value>Статистика достижений.</value>
</data>
<data name="ViewCardGachaStatisticsTitle" xml:space="preserve">
<value>保底计数</value>
<value>Pity Count</value>
</data>
<data name="ViewCardLaunchGameSelectAccountPlaceholder" xml:space="preserve">
<value>选择账号或直接启动</value>
<value>Выберите аккаунт для начала</value>
</data>
<data name="ViewControlBaseValueSliderLevel" xml:space="preserve">
<value>等级</value>
<value>Уровень</value>
</data>
<data name="ViewControlBaseValueSliderPromoted" xml:space="preserve">
<value>突破后</value>
<value>After Ascension</value>
</data>
<data name="ViewControlElevationText" xml:space="preserve">
<value>需要管理员权限</value>
</data>
<data name="ViewControlLoadingText" xml:space="preserve">
<value>加载中,请稍候</value>
<value>Загрузка, пожалуйста подождите</value>
</data>
<data name="ViewControlStatisticsCardBlueText" xml:space="preserve">
<value>三星</value>
<value>3-зв.</value>
</data>
<data name="ViewControlStatisticsCardGuaranteeText" xml:space="preserve">
<value>保底</value>
<value>Гарант</value>
</data>
<data name="ViewControlStatisticsCardOrangeAveragePullText" xml:space="preserve">
<value>五星平均抽数</value>
</data>
<data name="ViewControlStatisticsCardOrangeText" xml:space="preserve">
<value>五星</value>
<value>5-зв.</value>
</data>
<data name="ViewControlStatisticsCardPullText" xml:space="preserve">
<value></value>
<value>Pulls</value>
</data>
<data name="ViewControlStatisticsCardPurpleText" xml:space="preserve">
<value>四星</value>
<value>4 зв.</value>
</data>
<data name="ViewControlStatisticsCardToLastOrangeText" xml:space="preserve">
<value>距上个五星</value>
<value>Последний 5-зв.</value>
</data>
<data name="ViewControlStatisticsCardToLastPurpleText" xml:space="preserve">
<value>距上个四星</value>
<value>Последний 4-зв.</value>
</data>
<data name="ViewControlStatisticsCardUpAveragePullText" xml:space="preserve">
<value>UP 平均抽数</value>
<value>Средняя удача выпадения</value>
</data>
<data name="ViewControlStatisticsCardUpText" xml:space="preserve">
<value>UP</value>
</data>
<data name="ViewControlStatisticsSegmentedItemContentPrediction" xml:space="preserve">
<value>预测</value>
<value>Prediction</value>
</data>
<data name="ViewControlStatisticsSegmentedItemContentProportion" xml:space="preserve">
<value>比例</value>
<value>Масштаб</value>
</data>
<data name="ViewControlStatisticsSegmentedItemContentStatistics" xml:space="preserve">
<value>统计</value>
<value>Статистика</value>
</data>
<data name="ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed" xml:space="preserve">
<value>当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级</value>
</data>
<data name="ViewCultivationHeader" xml:space="preserve">
<value>养成计划</value>
<value>План разработки</value>
</data>
<data name="ViewDailyNoteHeader" xml:space="preserve">
<value>实时便笺</value>
</data>
<data name="ViewDataHeader" xml:space="preserve">
<value>数据</value>
<value>Data</value>
</data>
<data name="ViewDialogAchievementArchiveCreateInputPlaceholder" xml:space="preserve">
<value>在此处输入</value>
<value>Enter here</value>
</data>
<data name="ViewDialogAchievementArchiveCreateTitle" xml:space="preserve">
<value>设置成就存档的名称</value>
@@ -1187,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>
@@ -1332,10 +1338,10 @@
<value>用户使用协议与法律声明</value>
</data>
<data name="ViewGuideStepDocument" xml:space="preserve">
<value>文档</value>
<value>Документация</value>
</data>
<data name="ViewGuideStepEnvironment" xml:space="preserve">
<value>环境</value>
<value>Environments</value>
</data>
<data name="ViewGuideStepEnvironmentAfterInstallDescription" xml:space="preserve">
<value>安装完成后重启胡桃以查看是否正常生效</value>
@@ -1353,10 +1359,10 @@
<value>下载并自行安装运行时</value>
</data>
<data name="ViewGuideStepLanguage" xml:space="preserve">
<value>语言</value>
<value>Язык</value>
</data>
<data name="ViewGuideStepStaticResource" xml:space="preserve">
<value>资源</value>
<value>Assets</value>
</data>
<data name="ViewHutaoDatabaseHeader" xml:space="preserve">
<value>深渊统计</value>
@@ -1451,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>
@@ -1466,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>
@@ -1536,10 +1542,10 @@
<value>我已阅读并同意上方的条款</value>
</data>
<data name="ViewModelGuideActionComplete" xml:space="preserve">
<value>完成</value>
<value>Завершено</value>
</data>
<data name="ViewModelGuideActionNext" xml:space="preserve">
<value>下一步</value>
<value>Далее</value>
</data>
<data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve">
<value>下载资源文件中,请稍候</value>
@@ -1581,7 +1587,7 @@
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>操作完成</value>
<value>Действие завершено</value>
</data>
<data name="ViewModelSettingClearWebCacheFail" xml:space="preserve">
<value>清除失败,文件目录权限不足,请使用管理员模式重试</value>
@@ -1590,10 +1596,10 @@
<value>清除失败,找不到目录:{0}</value>
</data>
<data name="ViewModelSettingClearWebCacheSuccess" xml:space="preserve">
<value>清除完成</value>
<value>Очистка завершена</value>
</data>
<data name="ViewModelSettingCopyDeviceIdSuccess" xml:space="preserve">
<value>复制成功</value>
<value>Успешно скопировано</value>
</data>
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>创建桌面快捷方式失败</value>
@@ -1614,7 +1620,7 @@
<value>设置数据目录成功,重启以应用更改</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>保存游戏路径失败</value>
<value>Ошибка сохранения игрового пути</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>用户 [{0}] 添加成功</value>
@@ -1641,34 +1647,34 @@
<value>现在可以开始使用胡桃了</value>
</data>
<data name="ViewModelWelcomeDownloadCompleteTitle" xml:space="preserve">
<value>下载完成</value>
<value>Загрузка завершена</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
<value>Завершено</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>等待中</value>
<value>В процессе</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryException" xml:space="preserve">
<value>文件下载异常</value>
</data>
<data name="ViewPageAchievementAddArchive" xml:space="preserve">
<value>创建新存档</value>
<value>Создать новый архив</value>
</data>
<data name="ViewPageAchievementAddArchiveHint" xml:space="preserve">
<value>创建新存档以继续</value>
</data>
<data name="ViewPageAchievementExportLabel" xml:space="preserve">
<value>导出</value>
<value>Экспортировать</value>
</data>
<data name="ViewPageAchievementImportFromClipboard" xml:space="preserve">
<value>从剪贴板导入</value>
<value>Вставить из буфера обмена</value>
</data>
<data name="ViewPageAchievementImportFromFile" xml:space="preserve">
<value>从 UIAF 文件导入</value>
</data>
<data name="ViewPageAchievementImportLabel" xml:space="preserve">
<value>导入</value>
<value>Импорт</value>
</data>
<data name="ViewPageAchievementRemoveArchive" xml:space="preserve">
<value>删除当前存档</value>
@@ -1683,10 +1689,10 @@
<value>活动公告</value>
</data>
<data name="ViewPageAnnouncementGame" xml:space="preserve">
<value>游戏公告</value>
<value>Игровое Объявление</value>
</data>
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
<value>查看详情</value>
<value>Показать детали</value>
</data>
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
<value>圣遗物评分</value>
@@ -1698,7 +1704,7 @@
<value>当前角色与武器</value>
</data>
<data name="ViewPageAvatarPropertyCritScore" xml:space="preserve">
<value>双暴评分</value>
<value>Крит урон</value>
</data>
<data name="ViewPageAvatarPropertyDefaultDescription" xml:space="preserve">
<value>尚未获取任何角色信息</value>
@@ -1773,10 +1779,10 @@
<value>材料统计</value>
</data>
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>前往</value>
<value>Перейти</value>
</data>
<data name="ViewPageCultivationRemoveEntry" xml:space="preserve">
<value>删除清单</value>
<value>Удалить список</value>
</data>
<data name="ViewPageCultivationRemoveProject" xml:space="preserve">
<value>删除当前计划</value>
@@ -1788,13 +1794,13 @@
<value>添加任意武器到养成计划</value>
</data>
<data name="ViewPageDailyNoteAddEntry" xml:space="preserve">
<value>添加角色</value>
<value>Добавить роль</value>
</data>
<data name="ViewPageDailyNoteAddEntryHint" xml:space="preserve">
<value>添加角色以定时刷新</value>
</data>
<data name="ViewPageDailyNoteAddEntryToolTip" xml:space="preserve">
<value>添加</value>
<value>Добавить</value>
</data>
<data name="ViewPageDailyNoteAttendanceStatusInfo" xml:space="preserve">
<value>历练点获取详情</value>
@@ -1803,19 +1809,19 @@
<value>在实时便笺刷新后推送到指定的 Webhook</value>
</data>
<data name="ViewPageDailyNoteConfigWebhookHeader" xml:space="preserve">
<value>配置 Webhook</value>
<value>Настройка вебхуков</value>
</data>
<data name="ViewPageDailyNoteDataInteropHeader" xml:space="preserve">
<value>数据互操作</value>
</data>
<data name="ViewPageDailyNoteNotificationHeader" xml:space="preserve">
<value>通知</value>
<value>Уведомления</value>
</data>
<data name="ViewPageDailyNoteNotificationSetting" xml:space="preserve">
<value>通知设置</value>
<value>Настройки уведомлений</value>
</data>
<data name="ViewPageDailyNoteRefresh" xml:space="preserve">
<value>立即刷新</value>
<value>Обновить</value>
</data>
<data name="ViewPageDailyNoteRefreshTime" xml:space="preserve">
<value>刷新间隔时间</value>
@@ -1827,13 +1833,13 @@
<value>提醒通知</value>
</data>
<data name="ViewPageDailyNoteRemoveToolTip" xml:space="preserve">
<value>移除角色</value>
<value>Удалить роли</value>
</data>
<data name="ViewPageDailyNoteResinDiscountUsed" xml:space="preserve">
<value>本周已消耗减半次数</value>
</data>
<data name="ViewPageDailyNoteSettingAutoRefresh" xml:space="preserve">
<value>自动刷新</value>
<value>Автообновление</value>
</data>
<data name="ViewPageDailyNoteSettingAutoRefreshDescription" xml:space="preserve">
<value>间隔选定的时间后刷新添加的实时便笺</value>
@@ -1842,7 +1848,7 @@
<value>这些选项仅允许在非管理员模式下更改</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshHeader" xml:space="preserve">
<value>刷新</value>
<value>Обновить</value>
</data>
<data name="ViewPageDailyNoteSlientModeDescription" xml:space="preserve">
<value>在我游玩原神时不通知我</value>
@@ -1857,16 +1863,28 @@
<value>搜索问题与建议</value>
</data>
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
<value>基本信息</value>
<value>Основная информация</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>常用链接</value>
<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>
<value>Гайд по функциям</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>我们总是优先处理 GitHub 上的问题</value>
@@ -1875,7 +1893,7 @@
<value>开发路线规划</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>暂无搜索结果</value>
<value>Не найдено</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>胡桃服务可用性监控</value>
@@ -1887,13 +1905,13 @@
<value>全量刷新</value>
</data>
<data name="ViewPageGachaLogExportAction" xml:space="preserve">
<value>导出</value>
<value>Экспортировать</value>
</data>
<data name="ViewPageGachaLogHint" xml:space="preserve">
<value>尚未获取任何祈愿记录</value>
</data>
<data name="ViewPageGachaLogHutaoCloud" xml:space="preserve">
<value>胡桃云</value>
<value>Snap Hutao Cloud</value>
</data>
<data name="ViewPageGachaLogHutaoCloudAfdianPurchaseDescription" xml:space="preserve">
<value>前往爱发电购买相关服务</value>
@@ -1974,10 +1992,10 @@
<value>总览</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>统计</value>
<value>Статистика</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>武器</value>
<value>Оружие</value>
</data>
<data name="ViewPageHomeGreetingTextCommon1" xml:space="preserve">
<value>胡桃已经为你启动了 {0} 次游戏</value>
@@ -1986,7 +2004,7 @@
<value>你已经启动了胡桃 {0} 次</value>
</data>
<data name="ViewPageHomeGreetingTextDefault" xml:space="preserve">
<value>旅行者,欢迎来到提瓦特大陆!</value>
<value>Добро пожаловать в Тейват, Путешественник!</value>
</data>
<data name="ViewPageHomeGreetingTextEasterEgg" xml:space="preserve">
<value>你说的对,但是《胡桃》是由 DGP Studio 自主研发的一款...</value>
@@ -1995,10 +2013,10 @@
<value>呐,旅行者,这是你来到提瓦特大陆的第 {0} 天哦</value>
</data>
<data name="ViewPageHomeLaunchGameSettingAction" xml:space="preserve">
<value>设置</value>
<value>Настройки</value>
</data>
<data name="ViewPageHutaoDatabaseOverview" xml:space="preserve">
<value>详情</value>
<value>Детали</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewAvatarAppearanceRank" xml:space="preserve">
<value>角色出场</value>
@@ -2010,28 +2028,28 @@
<value>角色使用</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation0" xml:space="preserve">
<value>0 命</value>
<value>C 0</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation1" xml:space="preserve">
<value>1 命</value>
<value>C 1</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation2" xml:space="preserve">
<value>2 命</value>
<value>C 2</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation3" xml:space="preserve">
<value>3 命</value>
<value>C 3</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation4" xml:space="preserve">
<value>4 命</value>
<value>C 4</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation5" xml:space="preserve">
<value>5 命</value>
<value>C 5</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellation6" xml:space="preserve">
<value>6 命</value>
<value>C 6</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellationAvatar" xml:space="preserve">
<value>角色</value>
<value>Персонаж</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewConstellationHolding" xml:space="preserve">
<value>持有</value>
@@ -2064,22 +2082,22 @@
<value>总计深渊记录</value>
</data>
<data name="ViewPageHutaoDatabaseOverviewTeamAppearance" xml:space="preserve">
<value>队伍出场</value>
<value>Teams</value>
</data>
<data name="ViewPageHutaoPassportLoginHeader" xml:space="preserve">
<value>登录</value>
<value>Войти</value>
</data>
<data name="ViewPageHutaoPassportPasswordHint" xml:space="preserve">
<value>请输入密码</value>
<value>Введите пароль</value>
</data>
<data name="ViewPageHutaoPassportPasswordRequirementHint" xml:space="preserve">
<value>至少需要 8 个字符</value>
<value>Требуется не менее 8 символов</value>
</data>
<data name="ViewPageHutaoPassportRegisterHeader" xml:space="preserve">
<value>注册</value>
<value>Регистрация</value>
</data>
<data name="ViewPageHutaoPassportResetPasswordHeader" xml:space="preserve">
<value>重置密码</value>
<value>Сбросить пароль</value>
</data>
<data name="ViewPageHutaoPassportResetPasswordHint" xml:space="preserve">
<value>注销账号的数据将永远丢失,无法恢复</value>
@@ -2202,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>
@@ -2538,7 +2556,7 @@
<value>前往商店</value>
</data>
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 运行时</value>
<value>Webview2 Runtime</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>搭配圣遗物</value>
@@ -2637,10 +2655,10 @@
<value>删除转换服务器缓存</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>打开文件夹</value>
<value>Открыть папку</value>
</data>
<data name="ViewSettingHeader" xml:space="preserve">
<value>设置</value>
<value>Настройки</value>
</data>
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
<value>角色出场率 = 本层上阵该角色次数(层内重复出现只记一次)/ 深渊记录总数</value>
@@ -2649,13 +2667,13 @@
<value>角色使用率 = 本层上阵该角色次数(层内重复出现只记一次)/ 持有该角色的深渊记录总数</value>
</data>
<data name="ViewSpiralAbyssBattleHeader" xml:space="preserve">
<value>战斗数据</value>
<value>Результаты боя</value>
</data>
<data name="ViewSpiralAbyssBattleTimes" xml:space="preserve">
<value>战斗次数</value>
<value>Количество сражений</value>
</data>
<data name="ViewSpiralAbyssDamage" xml:space="preserve">
<value>最强一击</value>
<value>Максимальный урон</value>
</data>
<data name="ViewSpiralAbyssDefaultDescription" xml:space="preserve">
<value>尚未获取任何挑战记录</value>
@@ -2667,19 +2685,19 @@
<value>分期详情</value>
</data>
<data name="ViewSpiralAbyssEnergySkill" xml:space="preserve">
<value>元素爆发</value>
<value>Элементарная атака</value>
</data>
<data name="ViewSpiralAbyssHeader" xml:space="preserve">
<value>深境螺旋</value>
<value>Витая Бездна</value>
</data>
<data name="ViewSpiralAbyssHutaoStatistics" xml:space="preserve">
<value>本期统计</value>
<value>Статистика</value>
</data>
<data name="ViewSpiralAbyssMaxFloor" xml:space="preserve">
<value>最深抵达</value>
<value>Максимальный этаж Бездны</value>
</data>
<data name="ViewSpiralAbyssNormalSkill" xml:space="preserve">
<value>元素战技</value>
<value>Elemental Skills Cast</value>
</data>
<data name="ViewSpiralAbyssRecordBattleAvatars" xml:space="preserve">
<value>上场角色</value>
@@ -2688,7 +2706,7 @@
<value>攻击地脉镇石</value>
</data>
<data name="ViewSpiralAbyssRefresh" xml:space="preserve">
<value>刷新数据</value>
<value>Обновить данные</value>
</data>
<data name="ViewSpiralAbyssRefreshDescription" xml:space="preserve">
<value>同步米游社的深渊挑战记录</value>
@@ -2697,79 +2715,79 @@
<value>出战次数</value>
</data>
<data name="ViewSpiralAbyssStatistics" xml:space="preserve">
<value>统计数据</value>
<value>Статистика</value>
</data>
<data name="ViewSpiralAbyssTakeDamage" xml:space="preserve">
<value>最多承伤</value>
</data>
<data name="ViewSpiralAbyssTotalStar" xml:space="preserve">
<value>获得渊星</value>
<value>Stars Earned</value>
</data>
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
<value>上传数据</value>
<value>Загрузить данные</value>
</data>
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
<value>是否立即安装?</value>
<value>Установить сейчас?</value>
</data>
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
<value>胡桃 {0} 版本已准备就绪</value>
<value>Версия Snap Hutao {0} готова</value>
</data>
<data name="ViewTitleAutoClicking" xml:space="preserve">
<value>自动连点</value>
<value>Авто Клики</value>
</data>
<data name="ViewToolHeader" xml:space="preserve">
<value>工具</value>
<value>Инструменты</value>
</data>
<data name="ViewUserCookieOperation" xml:space="preserve">
<value>米游社</value>
<value>MiYouShe</value>
</data>
<data name="ViewUserCookieOperation2" xml:space="preserve">
<value>HoYoLAB</value>
</data>
<data name="ViewUserCookieOperation3" xml:space="preserve">
<value>当前用户</value>
<value>Текущий пользователь</value>
</data>
<data name="ViewUserCookieOperationGameRecordIndexAction" xml:space="preserve">
<value>旅行工具</value>
<value>Официальные инструменты</value>
</data>
<data name="ViewUserCookieOperationLoginMihoyoUserAction" xml:space="preserve">
<value>网页登录</value>
<value>Вход через браузер</value>
</data>
<data name="ViewUserCookieOperationLoginQRCodeAction" xml:space="preserve">
<value>扫码登录</value>
<value>Вход по QR-коду</value>
</data>
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
<value>手动输入</value>
<value>Ввести вручную</value>
</data>
<data name="ViewUserCookieOperationRefreshCookieAction" xml:space="preserve">
<value>刷新 Cookie</value>
<value>Обновить Cookie</value>
</data>
<data name="ViewUserCookieOperationSignInRewardAction" xml:space="preserve">
<value>领取签到奖励</value>
<value>Забрать награду за регистрацию</value>
</data>
<data name="ViewUserCopyCookieAction" xml:space="preserve">
<value>复制 Cookie</value>
<value>Скопировать Cookie</value>
</data>
<data name="ViewUserDefaultDescription" xml:space="preserve">
<value>请先登录</value>
<value>Пожалуйста, сначала войдите в приложение</value>
</data>
<data name="ViewUserDocumentationHeader" xml:space="preserve">
<value>文档</value>
<value>Документация</value>
</data>
<data name="ViewUserNoUserHint" xml:space="preserve">
<value>尚未登录</value>
<value>Вы не вошли в приложение</value>
</data>
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
<value>刷新 CookieToken 成功</value>
<value>Обновление токена Cookie выполнено успешно.</value>
</data>
<data name="ViewUserRefreshCookieTokenWarning" xml:space="preserve">
<value>刷新 CookieToken 失败</value>
<value>Не удалось обновить токен Cookie.</value>
</data>
<data name="ViewUserRemoveAction" xml:space="preserve">
<value>移除用户</value>
<value>Удалить пользователя</value>
</data>
<data name="ViewUserRole" xml:space="preserve">
<value>角色</value>
<value>Персонаж</value>
</data>
<data name="ViewUserUser" xml:space="preserve">
<value>用户</value>

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>
@@ -1187,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>
@@ -1284,7 +1290,7 @@
<value>使用米遊社掃描 QR 碼</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>防止你在无意间启用,请输入正在用的功能开关的&lt;b&gt;标题名称&lt;/b&gt;</value>
<value>防止你在無意間啟用,請輸入正在用的功能開關的&lt;b&gt;標題名稱&lt;/b&gt;</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>你正在啟用一個危險功能</value>
@@ -1451,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>
@@ -1466,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>
@@ -1728,7 +1734,7 @@
<value>同步角色天賦信息</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>从米游社原神战绩同步</value>
<value>從米遊社原神戰績同步</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>同步角色天賦外的大部分信息</value>
@@ -1860,28 +1866,40 @@
<value>基本資訊</value>
</data>
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>常用链接</value>
<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>
<value>與我們密切聯繫</value>
</data>
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
<value>功能指南</value>
</data>
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
<value>我们总是优先处理 GitHub 上的问题</value>
<value>我們總是優先處理 GitHub 上的問題</value>
</data>
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
<value>开发路线规划</value>
<value>開發路線規劃</value>
</data>
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
<value>暂无搜索结果</value>
<value>暫無搜尋結果</value>
</data>
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
<value>胡桃服可用性控</value>
<value>胡桃服可用性控</value>
</data>
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
<value>胡桃服</value>
<value>胡桃服</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>全量式重整</value>
@@ -2202,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>
@@ -2925,7 +2943,7 @@
<value>{0} 秒</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>验证失败,请手动验证或前往「米社-旅行工具-原神战绩-实时便笺」页面查看</value>
<value>驗證失敗,請手動進行驗證或前往「米社-旅行工具-原神戰績-實時便箋」頁面查看</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>錯誤的 UID 格式</value>
@@ -2994,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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

@@ -60,14 +60,16 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
{
if (!scheduleTaskInterop.RegisterForDailyNoteRefresh(SelectedRefreshTime.Value))
{
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteRegisterTaskFail);
return;
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteModifyTaskFail);
}
}
}
else
{
scheduleTaskInterop.UnregisterForDailyNoteRefresh();
if (!scheduleTaskInterop.UnregisterForDailyNoteRefresh())
{
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteModifyTaskFail);
}
}
OnPropertyChanged();
@@ -106,7 +108,7 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
}
else
{
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteRegisterTaskFail);
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteModifyTaskFail);
}
}
}

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