mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
101 Commits
feat/ident
...
feat/reord
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceb26bb669 | ||
|
|
a4e782da78 | ||
|
|
d5551e5cdf | ||
|
|
f016a4a27f | ||
|
|
8b931b6d89 | ||
|
|
b942ceb914 | ||
|
|
f7a49e52e0 | ||
|
|
d4bd610fe2 | ||
|
|
a3dcfd3804 | ||
|
|
592525d149 | ||
|
|
83c4598df5 | ||
|
|
aa680388ad | ||
|
|
ba4f59de30 | ||
|
|
8780cf385e | ||
|
|
431cdd1253 | ||
|
|
9a8827fb40 | ||
|
|
d88a6ca301 | ||
|
|
5d401794e5 | ||
|
|
26396443dc | ||
|
|
6b755d934d | ||
|
|
7d5b057269 | ||
|
|
917c173eb2 | ||
|
|
28d702422e | ||
|
|
7612ab5da3 | ||
|
|
ab436ecb2f | ||
|
|
457e3ff4d5 | ||
|
|
224c4e52ea | ||
|
|
2a5c7b21fd | ||
|
|
53f8291a66 | ||
|
|
b621d5406a | ||
|
|
12f4847aea | ||
|
|
39a3d31f38 | ||
|
|
70ac0b13a5 | ||
|
|
a3520a4991 | ||
|
|
aed0284e4b | ||
|
|
96ef07cbe5 | ||
|
|
d1f37f37ac | ||
|
|
1fe09f3069 | ||
|
|
32d9355c3a | ||
|
|
71f170d51e | ||
|
|
60015b6354 | ||
|
|
eecae3ea4f | ||
|
|
1831166f1e | ||
|
|
a98915ea24 | ||
|
|
0d46656f57 | ||
|
|
c814a5c28f | ||
|
|
9c3d59cc6f | ||
|
|
890cf3f3ea | ||
|
|
196bbb54c3 | ||
|
|
0481b9e474 | ||
|
|
c4f3eb68e8 | ||
|
|
c2e9f3a926 | ||
|
|
fb1fe3e40f | ||
|
|
75ed512e4a | ||
|
|
dd4dd33d93 | ||
|
|
1e216e9823 | ||
|
|
f823cb5f1a | ||
|
|
2c6d25f0a3 | ||
|
|
817f768263 | ||
|
|
2998fbb167 | ||
|
|
8f0f94054d | ||
|
|
17a5d4d3a2 | ||
|
|
a1c604e68a | ||
|
|
948ec9a822 | ||
|
|
f83174d690 | ||
|
|
d686debbfb | ||
|
|
45248d75e1 | ||
|
|
22646cfab2 | ||
|
|
d0237a3c89 | ||
|
|
73c80fad10 | ||
|
|
320bed9fcb | ||
|
|
bb12aca3b4 | ||
|
|
c7b5d98fb1 | ||
|
|
7cc96f94f2 | ||
|
|
b35355f9a3 | ||
|
|
745815657d | ||
|
|
d93a9f41f3 | ||
|
|
910f099c6d | ||
|
|
e68449ec0c | ||
|
|
e484fbed21 | ||
|
|
88af6d28a9 | ||
|
|
3ab34f0992 | ||
|
|
5e875a7f18 | ||
|
|
89d98748e8 | ||
|
|
d33cd894b9 | ||
|
|
f0c19b419e | ||
|
|
f1d9787e45 | ||
|
|
5f9b4a7cb2 | ||
|
|
8710150897 | ||
|
|
92c1b12764 | ||
|
|
d73bd557f3 | ||
|
|
777d7d1056 | ||
|
|
1a944dae9c | ||
|
|
a26c52ba97 | ||
|
|
5fab03d57e | ||
|
|
e8a459cb41 | ||
|
|
04df5a7bf1 | ||
|
|
1ebcc2fc89 | ||
|
|
e9917e788d | ||
|
|
9665876d52 | ||
|
|
2698761594 |
4
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
@@ -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
|
||||
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// COMCTL32
|
||||
DefSubclassProc
|
||||
RemoveWindowSubclass
|
||||
SetWindowSubclass
|
||||
|
||||
// DWMAPI
|
||||
DwmSetWindowAttribute
|
||||
|
||||
// GDI32
|
||||
GetDeviceCaps
|
||||
|
||||
// KERNEL32
|
||||
AllocConsole
|
||||
CloseHandle
|
||||
CreateEventW
|
||||
CreateRemoteThread
|
||||
FreeConsole
|
||||
GetConsoleMode
|
||||
GetModuleHandleW
|
||||
GetProcAddress
|
||||
GetStdHandle
|
||||
K32EnumProcessModules
|
||||
K32GetModuleBaseNameW
|
||||
K32GetModuleInformation
|
||||
ReadProcessMemory
|
||||
SetConsoleMode
|
||||
SetConsoleTitle
|
||||
SetEvent
|
||||
VirtualAlloc
|
||||
VirtualAllocEx
|
||||
VirtualFree
|
||||
VirtualFreeEx
|
||||
WaitForSingleObject
|
||||
WriteProcessMemory
|
||||
|
||||
// OLE32
|
||||
CoCreateInstance
|
||||
CoWaitForMultipleObjects
|
||||
|
||||
// SHELL32
|
||||
SHCreateItemFromParsingName
|
||||
|
||||
// USER32
|
||||
AttachThreadInput
|
||||
FindWindowExW
|
||||
GetCursorPos
|
||||
GetDC
|
||||
GetDpiForWindow
|
||||
GetForegroundWindow
|
||||
GetWindowPlacement
|
||||
GetWindowThreadProcessId
|
||||
ReleaseDC
|
||||
RegisterHotKey
|
||||
SendInput
|
||||
SetForegroundWindow
|
||||
UnregisterHotKey
|
||||
|
||||
// COM
|
||||
FileOpenDialog
|
||||
FileSaveDialog
|
||||
IFileOpenDialog
|
||||
IFileSaveDialog
|
||||
IPersistFile
|
||||
IShellLinkDataList
|
||||
IShellLinkW
|
||||
ShellLink
|
||||
SHELL_LINK_DATA_FLAGS
|
||||
|
||||
// WinRT
|
||||
IMemoryBufferByteAccess
|
||||
|
||||
// Const value
|
||||
E_FAIL
|
||||
INFINITE
|
||||
RPC_E_WRONG_THREAD
|
||||
MAX_PATH
|
||||
WM_GETMINMAXINFO
|
||||
WM_HOTKEY
|
||||
WM_NCRBUTTONDOWN
|
||||
WM_NCRBUTTONUP
|
||||
WM_NULL
|
||||
|
||||
// Type & Enum definition
|
||||
HRESULT_FROM_WIN32
|
||||
SLGP_FLAGS
|
||||
|
||||
// System.Threading
|
||||
LPTHREAD_START_ROUTINE
|
||||
|
||||
// UI.WindowsAndMessaging
|
||||
MINMAXINFO
|
||||
|
||||
// System.Com
|
||||
CWMO_FLAGS
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="NativeMethods.json" />
|
||||
<None Remove="NativeMethods.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="NativeMethods.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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)) };
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Windows.Win32.CsWin32.InteropServices;
|
||||
|
||||
internal class WinRTCustomMarshaler : ICustomMarshaler
|
||||
{
|
||||
private static readonly string? AssemblyFullName = typeof(Windows.Foundation.IMemoryBuffer).Assembly.FullName;
|
||||
|
||||
private readonly string className;
|
||||
private bool lookedForFromAbi;
|
||||
private MethodInfo? fromAbiMethod;
|
||||
|
||||
private WinRTCustomMarshaler(string className)
|
||||
{
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
public static ICustomMarshaler GetInstance(string cookie)
|
||||
{
|
||||
return new WinRTCustomMarshaler(cookie);
|
||||
}
|
||||
|
||||
public void CleanUpManagedData(object ManagedObj)
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUpNativeData(nint pNativeData)
|
||||
{
|
||||
Marshal.Release(pNativeData);
|
||||
}
|
||||
|
||||
public int GetNativeDataSize()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public nint MarshalManagedToNative(object ManagedObj)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public object MarshalNativeToManaged(nint thisPtr)
|
||||
{
|
||||
return className switch
|
||||
{
|
||||
"Windows.System.DispatcherQueueController" => Windows.System.DispatcherQueueController.FromAbi(thisPtr),
|
||||
_ => MarshalNativeToManagedSlow(thisPtr),
|
||||
};
|
||||
}
|
||||
|
||||
private object MarshalNativeToManagedSlow(nint pNativeData)
|
||||
{
|
||||
if (!lookedForFromAbi)
|
||||
{
|
||||
Type? type = Type.GetType($"{className}, {AssemblyFullName}");
|
||||
|
||||
fromAbiMethod = type?.GetMethod("FromAbi");
|
||||
lookedForFromAbi = true;
|
||||
}
|
||||
|
||||
if (fromAbiMethod is not null)
|
||||
{
|
||||
return fromAbiMethod.Invoke(default, new object[] { pNativeData })!;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Marshal.GetObjectForIUnknown(pNativeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -20,4 +20,4 @@ public sealed partial class SettingsExpanderHelper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace Snap.Hutao.Control;
|
||||
|
||||
[TemplateVisualState(Name = "LoadingIn", GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
|
||||
[TemplatePart(Name = "ContentGrid", Type = typeof(FrameworkElement))]
|
||||
internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shc="using:Snap.Hutao.Control">
|
||||
|
||||
<Style TargetType="shc:Loading">
|
||||
<Style BasedOn="{StaticResource DefaultLoadingStyle}" TargetType="shc:Loading"/>
|
||||
|
||||
<Style x:Key="DefaultLoadingStyle" TargetType="shc:Loading">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
|
||||
@@ -18,12 +18,15 @@ internal sealed class FontIconExtension : MarkupExtension
|
||||
/// </summary>
|
||||
public string Glyph { get; set; } = default!;
|
||||
|
||||
public double FontSize { get; set; } = 12;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
return new FontIcon()
|
||||
{
|
||||
Glyph = Glyph,
|
||||
FontSize = FontSize,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
21
src/Snap.Hutao/Snap.Hutao/Control/Panel/UniformPanel.cs
Normal file
21
src/Snap.Hutao/Snap.Hutao/Control/Panel/UniformPanel.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -156,7 +156,7 @@ internal sealed partial class HtmlDescriptionTextBlock : ContentControl
|
||||
text.Inlines.Add(new Run
|
||||
{
|
||||
Text = slice.ToString(),
|
||||
FontWeight = FontWeights.Bold,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
|
||||
<StackPanel Spacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="StackPanelSpacing8Template">
|
||||
<StackPanel Spacing="8"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="UniformGridColumns2Spacing2Template">
|
||||
<cwcont:UniformGrid
|
||||
ColumnSpacing="2"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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=""
|
||||
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=""
|
||||
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>
|
||||
@@ -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}"/>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls">
|
||||
<!-- Settings -->
|
||||
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
@@ -6,6 +9,12 @@
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
|
||||
<x:Double x:Key="SettingsCardContentControlMinWidth">120</x:Double>
|
||||
<x:Double x:Key="SettingsCardContentControlMinWidth2">160</x:Double>
|
||||
|
||||
<x:Double x:Key="SettingsCardContentControlSpacing">10</x:Double>
|
||||
|
||||
<Thickness x:Key="SettingsCardAlignSettingsExpanderPadding">16,16,44,16</Thickness>
|
||||
<Thickness x:Key="SettingsExpanderItemHasIconPadding">16,8,16,8</Thickness>
|
||||
|
||||
<Style
|
||||
x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
|
||||
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
|
||||
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
|
||||
<x:String x:Key="DocumentLink_Loopback">https://hut.ao/zh/advanced/FAQ.html#%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86%E4%BD%BF%E7%94%A8%E8%83%A1%E6%A1%83%E5%B7%A5%E5%85%B7%E7%AE%B1</x:String>
|
||||
|
||||
<!-- Other -->
|
||||
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
|
||||
@@ -27,6 +28,7 @@
|
||||
|
||||
<!-- EmotionIcon -->
|
||||
<x:String x:Key="UI_EmotionIcon25">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon25.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon52">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon52.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon71">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon71.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon250">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Snap.Hutao.Core.IO.Http.Proxy;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Globalization;
|
||||
|
||||
@@ -31,7 +34,7 @@ internal static class DependencyInjection
|
||||
.AddJsonOptions()
|
||||
.AddDatabase()
|
||||
.AddInjections()
|
||||
.AddHttpClients()
|
||||
.AddAllHttpClients()
|
||||
|
||||
// Discrete services
|
||||
.AddSingleton<IMessenger, WeakReferenceMessenger>()
|
||||
@@ -48,10 +51,10 @@ internal static class DependencyInjection
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeCulture(this IServiceProvider serviceProvider)
|
||||
{
|
||||
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
||||
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
|
||||
CultureOptions cultureOptions = serviceProvider.GetRequiredService<CultureOptions>();
|
||||
cultureOptions.SystemCulture = CultureInfo.CurrentCulture;
|
||||
|
||||
CultureInfo cultureInfo = appOptions.CurrentCulture;
|
||||
CultureInfo cultureInfo = cultureOptions.CurrentCulture;
|
||||
|
||||
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
|
||||
|
||||
@@ -38,8 +38,8 @@ internal static class IocConfiguration
|
||||
|
||||
private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder)
|
||||
{
|
||||
RuntimeOptions hutaoOptions = provider.GetRequiredService<RuntimeOptions>();
|
||||
string dbFile = System.IO.Path.Combine(hutaoOptions.DataFolder, "Userdata.db");
|
||||
RuntimeOptions runtimeOptions = provider.GetRequiredService<RuntimeOptions>();
|
||||
string dbFile = System.IO.Path.Combine(runtimeOptions.DataFolder, "Userdata.db");
|
||||
string sqlConnectionString = $"Data Source={dbFile}";
|
||||
|
||||
// Temporarily create a context
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO.Http.Proxy;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -14,12 +15,26 @@ internal static partial class IocHttpClientConfiguration
|
||||
{
|
||||
private const string ApplicationJson = "application/json";
|
||||
|
||||
/// <summary>
|
||||
/// 添加 <see cref="HttpClient"/>
|
||||
/// 此方法将会自动生成
|
||||
/// </summary>
|
||||
/// <param name="services">集合</param>
|
||||
/// <returns>可继续操作的集合</returns>
|
||||
public static IServiceCollection AddAllHttpClients(this IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.ConfigureHttpClientDefaults(clientBuilder =>
|
||||
{
|
||||
clientBuilder
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler())
|
||||
.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
|
||||
{
|
||||
HttpClientHandler clientHandler = (HttpClientHandler)handler;
|
||||
clientHandler.UseProxy = true;
|
||||
clientHandler.Proxy = provider.GetRequiredService<DynamicHttpProxy>();
|
||||
});
|
||||
})
|
||||
.AddHttpClients();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
|
||||
|
||||
/// <summary>
|
||||
@@ -29,10 +44,10 @@ internal static partial class IocHttpClientConfiguration
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
|
||||
{
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,6 +104,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
/// HoYoLAB web
|
||||
/// </summary>
|
||||
/// <param name="client">配置后的客户端</param>
|
||||
[SuppressMessage("", "IDE0051")]
|
||||
private static void XRpc4Configuration(HttpClient client)
|
||||
{
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
110
src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/DynamicHttpProxy.cs
Normal file
110
src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Proxy/DynamicHttpProxy.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Win32.Registry;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Http.Proxy;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class DynamicHttpProxy : ObservableObject, IWebProxy, IDisposable
|
||||
{
|
||||
private const string ProxySettingPath = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections";
|
||||
|
||||
private static readonly Lazy<MethodInfo> LazyConstructSystemProxyMethod = new(GetConstructSystemProxyMethod);
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly RegistryWatcher watcher;
|
||||
|
||||
private IWebProxy innerProxy = default!;
|
||||
|
||||
public DynamicHttpProxy(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
UpdateInnerProxy();
|
||||
|
||||
watcher = new(ProxySettingPath, OnSystemProxySettingsChanged);
|
||||
watcher.Start();
|
||||
}
|
||||
|
||||
public string CurrentProxyUri
|
||||
{
|
||||
get
|
||||
{
|
||||
Uri? proxyUri = GetProxy("https://hut.ao".ToUri());
|
||||
return proxyUri is null
|
||||
? SH.ViewPageFeedbackCurrentProxyNoProxyDescription
|
||||
: proxyUri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
|
||||
public IWebProxy InnerProxy
|
||||
{
|
||||
get => innerProxy;
|
||||
|
||||
[MemberNotNull(nameof(innerProxy))]
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(innerProxy, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
innerProxy = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ICredentials? Credentials
|
||||
{
|
||||
get => InnerProxy.Credentials;
|
||||
set => InnerProxy.Credentials = value;
|
||||
}
|
||||
|
||||
public Uri? GetProxy(Uri destination)
|
||||
{
|
||||
return InnerProxy.GetProxy(destination);
|
||||
}
|
||||
|
||||
public bool IsBypassed(Uri host)
|
||||
{
|
||||
return InnerProxy.IsBypassed(host);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(innerProxy as IDisposable)?.Dispose();
|
||||
watcher.Dispose();
|
||||
}
|
||||
|
||||
public void OnSystemProxySettingsChanged()
|
||||
{
|
||||
UpdateInnerProxy();
|
||||
|
||||
// TaskContext can't be injected directly since there are some recursive dependencies.
|
||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
taskContext.BeginInvokeOnMainThread(() => OnPropertyChanged(nameof(CurrentProxyUri)));
|
||||
}
|
||||
|
||||
private static MethodInfo GetConstructSystemProxyMethod()
|
||||
{
|
||||
Type? systemProxyInfoType = typeof(System.Net.Http.SocketsHttpHandler).Assembly.GetType("System.Net.Http.SystemProxyInfo");
|
||||
ArgumentNullException.ThrowIfNull(systemProxyInfoType);
|
||||
|
||||
MethodInfo? constructSystemProxyMethod = systemProxyInfoType.GetMethod("ConstructSystemProxy", BindingFlags.Static | BindingFlags.Public);
|
||||
ArgumentNullException.ThrowIfNull(constructSystemProxyMethod);
|
||||
|
||||
return constructSystemProxyMethod;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(innerProxy))]
|
||||
private void UpdateInnerProxy()
|
||||
{
|
||||
IWebProxy? proxy = LazyConstructSystemProxyMethod.Value.Invoke(default, default) as IWebProxy;
|
||||
ArgumentNullException.ThrowIfNull(proxy);
|
||||
|
||||
InnerProxy = proxy;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ internal readonly struct Delay
|
||||
/// <summary>
|
||||
/// 随机延迟
|
||||
/// </summary>
|
||||
/// <param name="minMilliSeconds">最小,闭</param>
|
||||
/// <param name="maxMilliSeconds">最小,开</param>
|
||||
/// <param name="min">最小,闭</param>
|
||||
/// <param name="max">最小,开</param>
|
||||
/// <returns>任务</returns>
|
||||
public static ValueTask Random(int minMilliSeconds, int maxMilliSeconds)
|
||||
public static ValueTask RandomMilliSeconds(int min, int max)
|
||||
{
|
||||
return Task.Delay((int)(System.Random.Shared.NextDouble() * (maxMilliSeconds - minMilliSeconds)) + minMilliSeconds).AsValueTask();
|
||||
return Task.Delay((int)(System.Random.Shared.NextDouble() * (max - min)) + min).AsValueTask();
|
||||
}
|
||||
|
||||
public static ValueTask FromSeconds(int seconds)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.Backdrop;
|
||||
|
||||
internal sealed class TransparentBackdrop : SystemBackdrop, IDisposable, IBackdropNeedEraseBackground
|
||||
{
|
||||
private readonly object compositorLock = new();
|
||||
|
||||
private Color tintColor;
|
||||
private Windows.UI.Composition.CompositionColorBrush? brush;
|
||||
private Windows.UI.Composition.Compositor? compositor;
|
||||
|
||||
public TransparentBackdrop()
|
||||
: this(Colors.Transparent)
|
||||
{
|
||||
}
|
||||
|
||||
public TransparentBackdrop(Color tintColor)
|
||||
{
|
||||
this.tintColor = tintColor;
|
||||
}
|
||||
|
||||
internal Windows.UI.Composition.Compositor Compositor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (compositor is null)
|
||||
{
|
||||
lock (compositorLock)
|
||||
{
|
||||
if (compositor is null)
|
||||
{
|
||||
DispatcherQueue.EnsureSystemDispatcherQueue();
|
||||
compositor = new Windows.UI.Composition.Compositor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return compositor;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
compositor?.Dispose();
|
||||
}
|
||||
|
||||
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop connectedTarget, XamlRoot xamlRoot)
|
||||
{
|
||||
brush ??= Compositor.CreateColorBrush(tintColor);
|
||||
connectedTarget.SystemBackdrop = brush;
|
||||
}
|
||||
|
||||
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop disconnectedTarget)
|
||||
{
|
||||
disconnectedTarget.SystemBackdrop = null;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
[HighQuality]
|
||||
internal enum BackdropType
|
||||
{
|
||||
Transparent = -1,
|
||||
|
||||
/// <summary>
|
||||
/// 无
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Win32;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.Graphics.Dwm;
|
||||
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
|
||||
using System.IO;
|
||||
using Windows.Graphics;
|
||||
using Windows.UI;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Dwm;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using static Windows.Win32.PInvoke;
|
||||
using static Snap.Hutao.Win32.DwmApi;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
@@ -53,10 +55,10 @@ internal sealed class WindowController
|
||||
|
||||
private void InitializeCore()
|
||||
{
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
window.AppWindow.Title = SH.FormatAppNameAndVersion(hutaoOptions.Version);
|
||||
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
|
||||
window.AppWindow.Title = SH.FormatAppNameAndVersion(runtimeOptions.Version);
|
||||
window.AppWindow.SetIcon(Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico"));
|
||||
ExtendsContentIntoTitleBar();
|
||||
|
||||
RecoverOrInitWindowSize();
|
||||
@@ -104,11 +106,11 @@ internal sealed class WindowController
|
||||
return;
|
||||
}
|
||||
|
||||
WINDOWPLACEMENT windowPlacement = Win32.StructMarshal.WINDOWPLACEMENT();
|
||||
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create();
|
||||
GetWindowPlacement(options.Hwnd, ref windowPlacement);
|
||||
|
||||
// prevent save value when we are maximized.
|
||||
if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
|
||||
if (!windowPlacement.ShowCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
|
||||
{
|
||||
double scale = 1.0 / options.GetRasterizationScale();
|
||||
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect().Scale(scale));
|
||||
@@ -157,6 +159,7 @@ internal sealed class WindowController
|
||||
{
|
||||
window.SystemBackdrop = backdropType switch
|
||||
{
|
||||
BackdropType.Transparent => new Backdrop.TransparentBackdrop(),
|
||||
BackdropType.MicaAlt => new MicaBackdrop() { Kind = MicaKind.BaseAlt },
|
||||
BackdropType.Mica => new MicaBackdrop() { Kind = MicaKind.Base },
|
||||
BackdropType.Acrylic => new DesktopAcrylicBackdrop(),
|
||||
@@ -193,7 +196,7 @@ internal sealed class WindowController
|
||||
private unsafe void UpdateImmersiveDarkMode(FrameworkElement titleBar, object discard)
|
||||
{
|
||||
BOOL isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(titleBar.ActualTheme);
|
||||
DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &isDarkMode, unchecked((uint)sizeof(BOOL)));
|
||||
DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, ref isDarkMode);
|
||||
}
|
||||
|
||||
private void UpdateDragRectangles()
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
|
||||
using System.Runtime.CompilerServices;
|
||||
using WinRT.Interop;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
@@ -16,4 +20,12 @@ internal static class WindowExtension
|
||||
WindowController windowController = new(window, window.WindowOptions, serviceProvider);
|
||||
WindowControllers.Add(window, windowController);
|
||||
}
|
||||
|
||||
public static void SetLayeredWindow(this Window window)
|
||||
{
|
||||
HWND hwnd = (HWND)WindowNative.GetWindowHandle(window);
|
||||
nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
|
||||
style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED;
|
||||
SetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing.Backdrop;
|
||||
using Snap.Hutao.Core.Windowing.HotKey;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using static Windows.Win32.PInvoke;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.UI.Shell;
|
||||
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
|
||||
using static Snap.Hutao.Win32.ComCtl32;
|
||||
using static Snap.Hutao.Win32.ConstValues;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
@@ -25,8 +28,8 @@ internal sealed class WindowSubclass : IDisposable
|
||||
private readonly IHotKeyController hotKeyController;
|
||||
|
||||
// We have to explicitly hold a reference to SUBCLASSPROC
|
||||
private SUBCLASSPROC? windowProc;
|
||||
private SUBCLASSPROC? legacyDragBarProc;
|
||||
private SUBCLASSPROC windowProc = default!;
|
||||
private SUBCLASSPROC legacyDragBarProc = default!;
|
||||
|
||||
public WindowSubclass(Window window, in WindowOptions options, IServiceProvider serviceProvider)
|
||||
{
|
||||
@@ -56,7 +59,7 @@ internal sealed class WindowSubclass : IDisposable
|
||||
}
|
||||
|
||||
titleBarHooked = false;
|
||||
HWND hwndDragBar = FindWindowEx(options.Hwnd, default, "DRAG_BAR_WINDOW_CLASS", default);
|
||||
HWND hwndDragBar = FindWindowExW(options.Hwnd, default, "DRAG_BAR_WINDOW_CLASS", default);
|
||||
|
||||
if (hwndDragBar.IsNull)
|
||||
{
|
||||
@@ -75,12 +78,12 @@ internal sealed class WindowSubclass : IDisposable
|
||||
hotKeyController.UnregisterAll();
|
||||
|
||||
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
|
||||
windowProc = null;
|
||||
windowProc = default!;
|
||||
|
||||
if (options.UseLegacyDragBarImplementation)
|
||||
{
|
||||
RemoveWindowSubclass(options.Hwnd, legacyDragBarProc, DragBarSubclassId);
|
||||
legacyDragBarProc = null;
|
||||
legacyDragBarProc = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ internal sealed class WindowSubclass : IDisposable
|
||||
{
|
||||
if (window is IMinMaxInfoHandler handler)
|
||||
{
|
||||
handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam.Value, options.GetRasterizationScale());
|
||||
handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, options.GetRasterizationScale());
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -110,6 +113,16 @@ internal sealed class WindowSubclass : IDisposable
|
||||
hotKeyController.OnHotKeyPressed(*(HotKeyParameter*)&lParam);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_ERASEBKGND:
|
||||
{
|
||||
if (window.SystemBackdrop is IBackdropNeedEraseBackground)
|
||||
{
|
||||
return (LRESULT)(int)BOOL.TRUE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,12 @@ internal static class CollectionsNameValue
|
||||
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(x.ToString(), x))];
|
||||
}
|
||||
|
||||
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> codiction)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return [.. Enum.GetValues<TEnum>().Where(codiction).Select(x => new NameValue<TEnum>(x.ToString(), x))];
|
||||
}
|
||||
|
||||
public static List<NameValue<TSource>> From<TSource>(IEnumerable<TSource> sources, Func<TSource, string> nameSelector)
|
||||
{
|
||||
return [.. sources.Select(x => new NameValue<TSource>(nameSelector(x), x))];
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Table("game_accounts")]
|
||||
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>
|
||||
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>, ICloneable<GameAccount>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -54,6 +54,17 @@ internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount,
|
||||
};
|
||||
}
|
||||
|
||||
public GameAccount Clone()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
AttachUid = AttachUid,
|
||||
Name = Name,
|
||||
MihoyoSDK = MihoyoSDK,
|
||||
Type = Type,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新绑定的Uid
|
||||
/// </summary>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
/// UIGF格式的信息
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, MetadataOptions, string>
|
||||
internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, CultureOptions, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户Uid
|
||||
@@ -65,12 +65,12 @@ internal sealed class UIGFInfo : IMappingFrom<UIGFInfo, RuntimeOptions, Metadata
|
||||
[JsonPropertyName("region_time_zone")]
|
||||
public int? RegionTimeZone { get; set; } = default!;
|
||||
|
||||
public static UIGFInfo From(RuntimeOptions runtimeOptions, MetadataOptions metadataOptions, string uid)
|
||||
public static UIGFInfo From(RuntimeOptions runtimeOptions, CultureOptions cultureOptions, string uid)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Uid = uid,
|
||||
Language = metadataOptions.LanguageCode,
|
||||
Language = cultureOptions.LanguageCode,
|
||||
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ExportApp = SH.AppName,
|
||||
ExportAppVersion = runtimeOptions.Version.ToString(),
|
||||
|
||||
@@ -64,14 +64,14 @@ internal sealed class UIIFInfo
|
||||
/// <returns>专用 UIGF 信息</returns>
|
||||
public static UIIFInfo From(IServiceProvider serviceProvider, string uid)
|
||||
{
|
||||
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
return new()
|
||||
{
|
||||
Uid = uid,
|
||||
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ExportApp = SH.AppName,
|
||||
ExportAppVersion = hutaoOptions.Version.ToString(),
|
||||
ExportAppVersion = runtimeOptions.Version.ToString(),
|
||||
UIIFVersion = UIIF.CurrentVersion,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, // 无垢之海
|
||||
]);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -82,4 +82,7 @@ internal enum WaveType
|
||||
|
||||
[LocalizationKey(nameof(SH.ModelMetadataTowerWaveTypeWave1Additional))]
|
||||
Wave1Additional = 1999,
|
||||
|
||||
[LocalizationKey(nameof(SH.ModelMetadataTowerWaveTypeWave99999))]
|
||||
Wave99999 = 99999,
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -247,7 +247,7 @@
|
||||
<value>Not refreshed</value>
|
||||
</data>
|
||||
<data name="ModelEntityDailyNoteRefreshTimeFormat" xml:space="preserve">
|
||||
<value>Refresh at {0:MM.dd HH:mm:ss}</value>
|
||||
<value>Refreshed at {0:MM.dd HH:mm:ss}</value>
|
||||
</data>
|
||||
<data name="ModelEntitySpiralAbyssScheduleFormat" xml:space="preserve">
|
||||
<value>Schedule {0}</value>
|
||||
@@ -738,10 +738,10 @@
|
||||
<value>Avatar Calculator: {0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
|
||||
<value>My Characters: N/A</value>
|
||||
<value>Battle Chronicle: Not Refreshed</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
|
||||
<value>My Characters: {0:MM-dd HH:mm}</value>
|
||||
<value>Battle Chronicle: {0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
|
||||
<value>Enka: N/A</value>
|
||||
@@ -870,11 +870,23 @@
|
||||
<value>No writing permission in file system, unable to start the server conversion.</value>
|
||||
</data>
|
||||
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
|
||||
<value>Querying Game Resource Information</value>
|
||||
<value>Download game resource index</value>
|
||||
</data>
|
||||
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
|
||||
<value>Failed to operate on game file: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
|
||||
<value>Failed to unlock frame rate limit</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
|
||||
<value>Game in process</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
|
||||
<value>Select Game Path</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
|
||||
<value>Failed to download game resource index: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
|
||||
<value>Game process closed</value>
|
||||
</data>
|
||||
@@ -1158,7 +1170,7 @@
|
||||
<value>Realm Currency Alarm Threshold</value>
|
||||
</data>
|
||||
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
|
||||
<value>Realm Currency Alarm Threshold</value>
|
||||
<value>Original Resin Alarm Threshold</value>
|
||||
</data>
|
||||
<data name="ViewDialogDailyNoteNotificationShowInHomeWidget" xml:space="preserve">
|
||||
<value>Show widget on Home page</value>
|
||||
@@ -1175,6 +1187,12 @@
|
||||
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
|
||||
<value>Realtime Note Webhook URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
|
||||
<value>You need other tools to restore the loopback after it is exempted</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
|
||||
<value>Confirm to Config Loopback Exemption</value>
|
||||
</data>
|
||||
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
|
||||
<value>Import wish history</value>
|
||||
</data>
|
||||
@@ -1271,6 +1289,12 @@
|
||||
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
||||
<value>Scan the QR code with MiHoYo BBS App</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
|
||||
<value>To avoid enable in a mistake, please input <b>title name</b> of feature you are enabling</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
|
||||
<value>You are Enabling a Dangerous Feature</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>This action is irreversible, and all user login status will be lost</value>
|
||||
</data>
|
||||
@@ -1292,6 +1316,9 @@
|
||||
<data name="ViewDialogUserTitle" xml:space="preserve">
|
||||
<value>Set Cookie</value>
|
||||
</data>
|
||||
<data name="ViewFeedbackHeader" xml:space="preserve">
|
||||
<value>Feedback Center</value>
|
||||
</data>
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>Wish History</value>
|
||||
</data>
|
||||
@@ -1368,7 +1395,7 @@
|
||||
<value>This operation is irreversible. The achievement archive will be lost.</value>
|
||||
</data>
|
||||
<data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve">
|
||||
<value>Are you sure you want to delete archive {0}?</value>
|
||||
<value>Are you sure to delete archive {0}?</value>
|
||||
</data>
|
||||
<data name="ViewModelAchievementUIAFExportPickerTitle" xml:space="preserve">
|
||||
<value>Export UIAF Json file to the selected path</value>
|
||||
@@ -1430,6 +1457,9 @@
|
||||
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
|
||||
<value>HoYoLab account does not support Realtime Notes verification</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
|
||||
<value>Failed to modify Scheduled Task</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
|
||||
<value>30 min | 3.75 Resin</value>
|
||||
</data>
|
||||
@@ -1445,14 +1475,11 @@
|
||||
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
|
||||
<value>8 min | 1 Resin</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
|
||||
<value>Failed to add Scheduled Task, please try again in administrator mode</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
|
||||
<value>Fetching Realtime Notes data, please wait</value>
|
||||
</data>
|
||||
<data name="ViewModelExportSuccessMessage" xml:space="preserve">
|
||||
<value>Successfully saved to selected patch</value>
|
||||
<value>Successfully saved to selected path</value>
|
||||
</data>
|
||||
<data name="ViewModelExportSuccessTitle" xml:space="preserve">
|
||||
<value>Export successfully</value>
|
||||
@@ -1491,7 +1518,7 @@
|
||||
<value>Failed to fetch wish history</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaLogRefreshOperationCancel" xml:space="preserve">
|
||||
<value>Wish history refresh is canceled unexpectedly</value>
|
||||
<value>Wish history refresh was canceled unexpectedly</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaLogRemoveArchiveDescription" xml:space="preserve">
|
||||
<value>This operation is irreversible. The wish history archive will be lost.</value>
|
||||
@@ -1541,6 +1568,9 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>Convert server failed</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>Identify Monitors</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
|
||||
<value>Unable to read game config file: {0}, file may be not exist not lack of user permission</value>
|
||||
</data>
|
||||
@@ -1574,6 +1604,12 @@
|
||||
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
|
||||
<value>Failed to create desktop shortcut</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
|
||||
<value>You will need to re-download needed files, are you sure to delete?</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
||||
<value>Delete Sever Conversion Client Cache</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
||||
<value>Used disk space: {0}</value>
|
||||
</data>
|
||||
@@ -1626,7 +1662,7 @@
|
||||
<value>Create New Archive</value>
|
||||
</data>
|
||||
<data name="ViewPageAchievementAddArchiveHint" xml:space="preserve">
|
||||
<value>Create new archive to continue</value>
|
||||
<value>Create New Archive to Continue</value>
|
||||
</data>
|
||||
<data name="ViewPageAchievementExportLabel" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
@@ -1647,7 +1683,7 @@
|
||||
<value>Name, description, version or ID</value>
|
||||
</data>
|
||||
<data name="ViewPageAchievementSortIncompletedItemsFirst" xml:space="preserve">
|
||||
<value>Prefer incomplete</value>
|
||||
<value>Prefer Incomplete</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementActivity" xml:space="preserve">
|
||||
<value>Event Notice</value>
|
||||
@@ -1671,7 +1707,7 @@
|
||||
<value>CRIT Rating</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyDefaultDescription" xml:space="preserve">
|
||||
<value>No character data fetched</value>
|
||||
<value>No Character Data Fetched</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyExportAsImage" xml:space="preserve">
|
||||
<value>Export as Image</value>
|
||||
@@ -1698,7 +1734,7 @@
|
||||
<value>Sync character talents data</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
|
||||
<value>Sync from MiHoYo BBS My Characters</value>
|
||||
<value>Sync from MiHoYo BBS Battle Chronicle</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
|
||||
<value>Sync most data other than character talent</value>
|
||||
@@ -1722,7 +1758,7 @@
|
||||
<value>Create</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationAddProjectContinue" xml:space="preserve">
|
||||
<value>Create plan to continue</value>
|
||||
<value>Create Plan to Continue</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationAddProjectDescription" xml:space="preserve">
|
||||
<value>You can add development plan items later from other pages</value>
|
||||
@@ -1823,6 +1859,48 @@
|
||||
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
|
||||
<value>Verify Current User and Role</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
|
||||
<value>Search for questions and suggestions</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
|
||||
<value>Basic Information</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
|
||||
<value>Useful Links</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
|
||||
<value>Current Proxy</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
|
||||
<value>No Proxy</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
|
||||
<value>Exempted</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
|
||||
<value>Config Loopback Exemption</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
|
||||
<value>Keep in touch with us</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
|
||||
<value>Feature Documents</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
|
||||
<value>We always prioritize issues reported on GitHub</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
|
||||
<value>Development Roadmap</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
|
||||
<value>No Result</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
|
||||
<value>Snap Hutao Service Availability Monitor</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
|
||||
<value>Snap Hutao Services</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
|
||||
<value>Full Refresh</value>
|
||||
</data>
|
||||
@@ -1875,7 +1953,7 @@
|
||||
<value>Input</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRecoverFromHutaoCloudDescription" xml:space="preserve">
|
||||
<value>Recover Wish Record from Snap Hutao Cloud</value>
|
||||
<value>Recover Wish Records from Snap Hutao Cloud</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefresh" xml:space="preserve">
|
||||
<value>Refresh</value>
|
||||
@@ -2055,10 +2133,10 @@
|
||||
<value>Borderless</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceCloudThirdPartyMobileDescription" xml:space="preserve">
|
||||
<value>Enable touchscreen layout, but the keyboard & mouse are no longer usable.</value>
|
||||
<value>Enable touchscreen layout, but the keyboard & mouse are no longer usable</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceExclusiveDescription" xml:space="preserve">
|
||||
<value>Incompatible with embedded browsers; operations like switching window may cause the game to crash.</value>
|
||||
<value>Incompatible with embedded browsers; operations like switching window may cause the game to crash</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceExclusiveHeader" xml:space="preserve">
|
||||
<value>Exclusive Fullscreen</value>
|
||||
@@ -2088,7 +2166,7 @@
|
||||
<value>Modify its default behavior at game startup</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
|
||||
<value>Start-up Arguments</value>
|
||||
<value>Launch Parameters</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
|
||||
<value>General</value>
|
||||
@@ -2142,7 +2220,7 @@
|
||||
<value>Resource Download</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
|
||||
<value>Client</value>
|
||||
<value>Full Package</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>Pre-download</value>
|
||||
@@ -2192,6 +2270,12 @@
|
||||
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
|
||||
<value>Enabled</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
|
||||
<value>Take advantage of displays that support HDR for brighter, more vivid, and more detailed pictures</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
|
||||
<value>Windows HDR</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Enter your HoYoLab UID</value>
|
||||
</data>
|
||||
@@ -2205,7 +2289,7 @@
|
||||
<value>Open Screenshots Folder</value>
|
||||
</data>
|
||||
<data name="ViewPageResetGamePathAction" xml:space="preserve">
|
||||
<value>Select game path</value>
|
||||
<value>Select Game Path</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingAboutHeader" xml:space="preserve">
|
||||
<value>About Snap Hutao</value>
|
||||
@@ -2409,7 +2493,7 @@
|
||||
<value>After a full reading of the Genshin Impact and Snap Hutao user agreements, I choose to enable「Game Launcher - Advanced Features」.</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
|
||||
<value>Enable Advanced Features</value>
|
||||
<value>Advanced Features</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
|
||||
<value>Change Auto Click Shortcut</value>
|
||||
@@ -2520,7 +2604,7 @@
|
||||
<value>Quotes</value>
|
||||
</data>
|
||||
<data name="ViewPageWiKiAvatarSpecialFoodTitle" xml:space="preserve">
|
||||
<value>Special Food</value>
|
||||
<value>Special Dish</value>
|
||||
</data>
|
||||
<data name="ViewPageWiKiAvatarStoriesHeader" xml:space="preserve">
|
||||
<value>Stories</value>
|
||||
@@ -2564,9 +2648,18 @@
|
||||
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
|
||||
<value>Debug Console</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
|
||||
<value>Cache file are downloaded for server conversion in Game Launcher</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
|
||||
<value>Delete Server Conversion Cache</value>
|
||||
</data>
|
||||
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
|
||||
<value>Open Folder</value>
|
||||
</data>
|
||||
<data name="ViewSettingHeader" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
|
||||
<value>Character Appearance Rate = Character Appearance in this Floor (only count for 1 if repeated) / Total Number of Abyss Record of this Floor</value>
|
||||
</data>
|
||||
@@ -2661,7 +2754,7 @@
|
||||
<value>Web Login</value>
|
||||
</data>
|
||||
<data name="ViewUserCookieOperationLoginQRCodeAction" xml:space="preserve">
|
||||
<value>Login via scanning QR code</value>
|
||||
<value>Login via QR code</value>
|
||||
</data>
|
||||
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
|
||||
<value>Input Manually</value>
|
||||
@@ -2747,6 +2840,9 @@
|
||||
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
|
||||
<value>End in {0} hours</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
|
||||
<value>Failed to open clipboard</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>Copied to clipboard</value>
|
||||
</data>
|
||||
@@ -2811,10 +2907,10 @@
|
||||
<value>{0} day</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteResinRecoveryCompleted" xml:space="preserve">
|
||||
<value>Original Resin is full</value>
|
||||
<value>Original Resin is Full</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteResinRecoveryFormat" xml:space="preserve">
|
||||
<value>Will be replenished in {0} {1:HH:mm}</value>
|
||||
<value>Will be Replenished in {0} {1:HH:mm}</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteTransformerAppend" xml:space="preserve">
|
||||
<value>Ready for use again after</value>
|
||||
@@ -2847,7 +2943,7 @@
|
||||
<value>{0} Seconds</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
|
||||
<value>Verification failed. Please check MiHoYo BBS - My Character - Realtime Notes manually</value>
|
||||
<value>Verification failed. Please check MiHoYo BBS - Toolbox - Realtime Notes manually</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
|
||||
<value>Wrong UID format</value>
|
||||
@@ -2916,7 +3012,7 @@
|
||||
<value>Snap Hutao server is under maintenance</value>
|
||||
</data>
|
||||
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
|
||||
<value>Verification failed. Please verify manually or check MiHoYo BBS - My Characters page</value>
|
||||
<value>Verification failed. Please verify manually or check MiHoYo BBS - Battle Chronicle page</value>
|
||||
</data>
|
||||
<data name="WebResponseFormat" xml:space="preserve">
|
||||
<value>Return Code: {0} | Message: {1}</value>
|
||||
@@ -2927,4 +3023,7 @@
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{1}] network request exception in [{0}] please try again later</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
|
||||
<value>Monitor ID</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<value>Snap Hutao Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao Dev {0} [Administrator]</value>
|
||||
<value>胡桃Dev {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao {0} [Administrator]</value>
|
||||
@@ -738,10 +738,10 @@
|
||||
<value>Kalkulator Avatar: {0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
|
||||
<value>Karakter Saya: N/A</value>
|
||||
<value>Battle Chronicle: Belum Disegarkan</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
|
||||
<value>Karakter Saya: {0:MM-dd HH:mm}</value>
|
||||
<value>Battle Chronicle: {0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
|
||||
<value>Enka: N/A</value>
|
||||
@@ -870,11 +870,23 @@
|
||||
<value>Tidak ada izin menulis dalam sistem berkas, tidak dapat memulai konversi server.</value>
|
||||
</data>
|
||||
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
|
||||
<value>Mencari Informasi Sumber Daya Game</value>
|
||||
<value>Unduh indeks sumber daya game</value>
|
||||
</data>
|
||||
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
|
||||
<value>Gagal melakukan operasi pada file game: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
|
||||
<value>Gagal membuka batas frame rate</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
|
||||
<value>Proses game sedang berjalan</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
|
||||
<value>Mohon pilih path game</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
|
||||
<value>Gagal mengunduh indeks sumber daya game: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
|
||||
<value>Proses game ditutup</value>
|
||||
</data>
|
||||
@@ -1175,6 +1187,12 @@
|
||||
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
|
||||
<value>Catatan Realtime Webhook URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
|
||||
<value>解除限制后需要使用其他工具恢复限制</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
|
||||
<value>是否解除 Loopback 限制</value>
|
||||
</data>
|
||||
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
|
||||
<value>Mengimpor riwayat wish</value>
|
||||
</data>
|
||||
@@ -1271,6 +1289,12 @@
|
||||
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
||||
<value>Pindai kode QR dengan Aplikasi MiHoYo BBS</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
|
||||
<value>Untuk menghindari mengaktifkan secara keliru, harap masukkan <b>nama judul</b> dari fitur yang Anda aktifkan</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
|
||||
<value>Anda Mengaktifkan Fitur Berbahaya</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>Tindakan ini tidak dapat dibatalkan, dan semua status login pengguna akan hilang.</value>
|
||||
</data>
|
||||
@@ -1292,6 +1316,9 @@
|
||||
<data name="ViewDialogUserTitle" xml:space="preserve">
|
||||
<value>Setel Cookie</value>
|
||||
</data>
|
||||
<data name="ViewFeedbackHeader" xml:space="preserve">
|
||||
<value>Pusat Umpan Balik</value>
|
||||
</data>
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>Riwayat Wish</value>
|
||||
</data>
|
||||
@@ -1430,6 +1457,9 @@
|
||||
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
|
||||
<value>Akun HoYoLab tidak mendukung verifikasi Catatan Realtime.</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
|
||||
<value>Gagal mengubah tugas terjadwal</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
|
||||
<value>30 min | 3.75 Resin</value>
|
||||
</data>
|
||||
@@ -1445,9 +1475,6 @@
|
||||
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
|
||||
<value>8 min | 1 Resin</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
|
||||
<value>Gagal menambahkan Tugas Terjadwal, silakan coba lagi dalam mode administrator.</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
|
||||
<value>Mengambil data Catatan Realtime, mohon tunggu.</value>
|
||||
</data>
|
||||
@@ -1541,6 +1568,9 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>Konversi server gagal</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>Identifikasi Monitor</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
|
||||
<value>Tidak dapat membaca file konfigurasi game: {0}, file mungkin tidak ada atau kurang izin pengguna</value>
|
||||
</data>
|
||||
@@ -1574,6 +1604,12 @@
|
||||
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
|
||||
<value>Gagal membuat shortcut di desktop</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
|
||||
<value>Anda akan diminta mengunduh file yang diperlukan, Yakin ingin menghapus?</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
||||
<value>Menghapus Cache Server Konfersi</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
||||
<value>Ruang disk yang digunakan: {0}</value>
|
||||
</data>
|
||||
@@ -1698,7 +1734,7 @@
|
||||
<value>Sinkronisasi Data Talenta Karakter</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
|
||||
<value>Sinkronkan dari Karakter Saya MiHoYo BBS</value>
|
||||
<value>Sinkronisasi dari MiHoYo BBS Battle Chronicle</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
|
||||
<value>Sinkronkan sebagian besar data kecuali talent karakter.</value>
|
||||
@@ -1823,6 +1859,48 @@
|
||||
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
|
||||
<value>Verifikasi Pengguna dan Role Saat Ini</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
|
||||
<value>Cari pertanyaan dan saran</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
|
||||
<value>Informasi Dasar</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
|
||||
<value>Tautan Berguna</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
|
||||
<value>当前代理</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
|
||||
<value>无代理</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
|
||||
<value>已解除</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
|
||||
<value>解除 Loopback 限制</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
|
||||
<value>Tetap berhubungan dengan kami</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
|
||||
<value>Dokumen Fitur</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
|
||||
<value>Kami selalu memberikan prioritas pada masalah yang dilaporkan di GitHub.</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
|
||||
<value>Roadmap Pengembangan</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
|
||||
<value>Tidak ada hasil</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
|
||||
<value>Pemantau Ketersediaan Layanan Snap Hutao</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
|
||||
<value>Servis Snap Hutao</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
|
||||
<value>Segarkan Seutuhnya</value>
|
||||
</data>
|
||||
@@ -2142,7 +2220,7 @@
|
||||
<value>Pengunduhan Sumber Daya</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
|
||||
<value>Klien</value>
|
||||
<value>完整包</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>Prapengunduhan</value>
|
||||
@@ -2192,6 +2270,12 @@
|
||||
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
|
||||
<value>Aktifkan</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
|
||||
<value>Manfaatkan tampilan yang mendukung rentang dinamis tinggi untuk gambar yang lebih terang, lebih jelas, dan lebih detail</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
|
||||
<value>Windows HDR</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Masukkan UID HoYoLab Anda</value>
|
||||
</data>
|
||||
@@ -2564,9 +2648,18 @@
|
||||
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
|
||||
<value>Konsol debug</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
|
||||
<value>File Cache diunduh untuk Server Konfersi pada Game Launcher</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
|
||||
<value>Menghapus Cache Server Konfersi</value>
|
||||
</data>
|
||||
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
|
||||
<value>Buka berkas</value>
|
||||
</data>
|
||||
<data name="ViewSettingHeader" xml:space="preserve">
|
||||
<value>Pengaturan</value>
|
||||
</data>
|
||||
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
|
||||
<value>Rasio Penampilan Karakter = Penampilan Karakter di Lantai Ini (hanya dihitung sekali jika berulang) / Total Jumlah Rekor Abyss di Lantai Ini</value>
|
||||
</data>
|
||||
@@ -2747,6 +2840,9 @@
|
||||
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
|
||||
<value>Selesai {0} jam</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
|
||||
<value>Gagal membuka clipboard</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>Disalin ke clipboard</value>
|
||||
</data>
|
||||
@@ -2847,7 +2943,7 @@
|
||||
<value>{0} detik</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
|
||||
<value>Verifikasi gagal. Silakan periksa MiHoYo BBS - Karakter Saya - Catatan Realtime secara manual.</value>
|
||||
<value>Verifikasi gagal. Harap periksa MiHoYo BBS - Toolbox - Catatan Real-time secara manual.</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
|
||||
<value>Format UID Salah</value>
|
||||
@@ -2916,7 +3012,7 @@
|
||||
<value>Server Snap Hutao sedang dalam perbaikan</value>
|
||||
</data>
|
||||
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
|
||||
<value>Verifikasi gagal. Harap verifikasi secara manual atau periksa halaman Karakter Saya di MiHoYo BBS.</value>
|
||||
<value>Verifikasi gagal. Harap verifikasi secara manual atau periksa halaman MiHoYo BBS - Battle Chronicle.</value>
|
||||
</data>
|
||||
<data name="WebResponseFormat" xml:space="preserve">
|
||||
<value>Kode Kembali: {0} | Pesan: {1}</value>
|
||||
@@ -2927,4 +3023,7 @@
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{1}] pengecualian permintaan jaringan di [{0}], harap coba lagi nanti</value>
|
||||
</data>
|
||||
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
|
||||
<value>ID Monitor</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -121,7 +121,7 @@
|
||||
<value>胡桃 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃Dev {0} [管理者]</value>
|
||||
<value>胡桃 Dev {0} [管理者]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [管理者]</value>
|
||||
@@ -738,10 +738,10 @@
|
||||
<value>育成計算: {0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
|
||||
<value>所持キャラ:未更新</value>
|
||||
<value>原神の記録: 更新していません</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
|
||||
<value>所持キャラ:{0:MM-dd HH:mm}</value>
|
||||
<value>原神の記録:{0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
|
||||
<value>キャラクターラインナップ:未更新</value>
|
||||
@@ -870,11 +870,23 @@
|
||||
<value>ファイル書き込みの権限が無いため、サーバー変換機能を使用できません</value>
|
||||
</data>
|
||||
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
|
||||
<value>ゲームのリソース情報を確認</value>
|
||||
<value>ゲームリソースのインデックスをダウンロード</value>
|
||||
</data>
|
||||
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
|
||||
<value>ゲームファイルの操作に失敗しました。: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
|
||||
<value>FPS上限の解放に失敗しました</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
|
||||
<value>ゲームは実行中です</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
|
||||
<value>ゲームのパスを選択してください</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
|
||||
<value>ゲームリソースのインデックスのダウンロードに失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
|
||||
<value>ゲームプロセスが終了しました</value>
|
||||
</data>
|
||||
@@ -1175,6 +1187,12 @@
|
||||
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
|
||||
<value>リアルタイムノートのWebhook URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
|
||||
<value>解除限制后需要使用其他工具恢复限制</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
|
||||
<value>是否解除 Loopback 限制</value>
|
||||
</data>
|
||||
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
|
||||
<value>祈願記録をインポート</value>
|
||||
</data>
|
||||
@@ -1271,6 +1289,12 @@
|
||||
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
||||
<value>MiHoYo BBS を使用して QR コードをスキャンします</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
|
||||
<value>謝って有効にしないようにするためには、有効にする機能の<b>タイトル名</b>を入力してください</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
|
||||
<value>危険な機能を有効にしています</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>この操作は取り消せません。すべてのユーザーのログイン状態が解除されます。</value>
|
||||
</data>
|
||||
@@ -1292,6 +1316,9 @@
|
||||
<data name="ViewDialogUserTitle" xml:space="preserve">
|
||||
<value>クッキーを設定</value>
|
||||
</data>
|
||||
<data name="ViewFeedbackHeader" xml:space="preserve">
|
||||
<value>フィードバック センター</value>
|
||||
</data>
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>祈願履歴</value>
|
||||
</data>
|
||||
@@ -1430,6 +1457,9 @@
|
||||
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
|
||||
<value>HoYoLabユーザーのリアルタイムノートはサポートしていません。</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
|
||||
<value>スケジュールされたタスクの変更に失敗しました</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
|
||||
<value>30 分 | 3.75樹脂</value>
|
||||
</data>
|
||||
@@ -1445,9 +1475,6 @@
|
||||
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
|
||||
<value>8 分 | 1樹脂</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
|
||||
<value>タスク スケジューラーへの追加に失敗しました。管理者モードで再度お試しください。</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
|
||||
<value>リアルタイムノートの情報を取得中、しばらくお待ちください</value>
|
||||
</data>
|
||||
@@ -1541,6 +1568,9 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>サーバーの切り替えができませんでした</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>モニターの識別</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
|
||||
<value>ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが無いか、権限が不足している可能性があります。</value>
|
||||
</data>
|
||||
@@ -1574,6 +1604,12 @@
|
||||
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
|
||||
<value>デスクトップへのショートカット作成に失敗しました</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
|
||||
<value>必要なファイルを再度ダウンロードする必要があります、削除してもよろしいですか?</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
||||
<value>サーバー変換用キャッシュファイルの削除</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
||||
<value>使用済みディスク容量: {0}</value>
|
||||
</data>
|
||||
@@ -1698,7 +1734,7 @@
|
||||
<value>キャラクターの天賦情報を同期</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
|
||||
<value>MiHoYo BBSから所持キャラを同期</value>
|
||||
<value>MiHoYo BBSから原神の記録を同期</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
|
||||
<value>キャラ天賦以外の情報を概ね同期</value>
|
||||
@@ -1823,6 +1859,48 @@
|
||||
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
|
||||
<value>現在のユーザーとUIDを確認する</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
|
||||
<value>質問や提案を検索</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
|
||||
<value>基本情報</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
|
||||
<value>よく使われるリンク</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
|
||||
<value>当前代理</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
|
||||
<value>无代理</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
|
||||
<value>已解除</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
|
||||
<value>解除 Loopback 限制</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
|
||||
<value>引き続き連絡をしてください</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
|
||||
<value>機能ガイド</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
|
||||
<value>Githubでは報告された問題を常に優先しています</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
|
||||
<value>開発ロードマップ</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
|
||||
<value>検索結果はありません</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
|
||||
<value>胡桃のサービス状態</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
|
||||
<value>胡桃サービス</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
|
||||
<value>すべて更新</value>
|
||||
</data>
|
||||
@@ -2142,7 +2220,7 @@
|
||||
<value>リソースダウンロード</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
|
||||
<value>クライアント</value>
|
||||
<value>完整包</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>事前ダウンロード</value>
|
||||
@@ -2192,6 +2270,12 @@
|
||||
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
|
||||
<value>有効</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
|
||||
<value>HDRをサポートするディスプレイを活用して、より明るく鮮やかなグラフィックを実現します。</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
|
||||
<value>Windows HDR</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>HoYoLab UIDを入力してください</value>
|
||||
</data>
|
||||
@@ -2205,7 +2289,7 @@
|
||||
<value>スクリーンショットフォルダを開く</value>
|
||||
</data>
|
||||
<data name="ViewPageResetGamePathAction" xml:space="preserve">
|
||||
<value>ゲームのフォルダを選択してください。</value>
|
||||
<value>ゲームフォルダを選択</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingAboutHeader" xml:space="preserve">
|
||||
<value>胡桃について</value>
|
||||
@@ -2564,9 +2648,18 @@
|
||||
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
|
||||
<value>デバッグコンソール</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
|
||||
<value>サーバー変換を使用すると、対応するサーバーのゲームクライアントのキャッシュファイルが生成されます</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
|
||||
<value>サーバー変換のキャッシュを削除</value>
|
||||
</data>
|
||||
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
|
||||
<value>フォルダを開く</value>
|
||||
</data>
|
||||
<data name="ViewSettingHeader" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
|
||||
<value>キャラクター出場率 = この階層における出場回数(最初の一回のみカウント) / この階層のアップロード総数</value>
|
||||
</data>
|
||||
@@ -2747,6 +2840,9 @@
|
||||
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
|
||||
<value>{0} 時間後に終了</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
|
||||
<value>クリップボードを開けませんでした</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>クリップボードにコピーしました。</value>
|
||||
</data>
|
||||
@@ -2847,7 +2943,7 @@
|
||||
<value>{0} 秒</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
|
||||
<value>認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認し、認証を行ってください</value>
|
||||
<value>認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認してください</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
|
||||
<value>UIDは正しくありません</value>
|
||||
@@ -2916,7 +3012,7 @@
|
||||
<value>胡桃サーバがメンテナンス中です</value>
|
||||
</data>
|
||||
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
|
||||
<value>認証に失敗しました。 手動で認証するか、MiHoYo BBS - 戦績 を確認してください。</value>
|
||||
<value>認証に失敗しました。 手動で認証するか、MiHoYo BBS - 戦績 - マイ キャラクター を確認してください。</value>
|
||||
</data>
|
||||
<data name="WebResponseFormat" xml:space="preserve">
|
||||
<value>リターンコード:{0} | メッセージ:{1}</value>
|
||||
@@ -2927,4 +3023,7 @@
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{0}] の[{1}] のリクエストにエラーが発生、時間をおいてから試してください</value>
|
||||
</data>
|
||||
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
|
||||
<value>モニターID</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -738,10 +738,10 @@
|
||||
<value>养成计算:{0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
|
||||
<value>我的角色:尚未刷新</value>
|
||||
<value>原神战绩:尚未刷新</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
|
||||
<value>我的角色:{0:MM-dd HH:mm}</value>
|
||||
<value>原神战绩:{0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
|
||||
<value>角色橱窗:尚未刷新</value>
|
||||
@@ -870,11 +870,23 @@
|
||||
<value>文件系统权限不足,无法转换服务器</value>
|
||||
</data>
|
||||
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
|
||||
<value>게임 리소스 정보 조회</value>
|
||||
<value>下载游戏资源索引</value>
|
||||
</data>
|
||||
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
|
||||
<value>게임 파일 작업 실패:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameFpsUnlockFailed" xml:space="preserve">
|
||||
<value>解锁帧率上限失败</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameIsRunning" xml:space="preserve">
|
||||
<value>游戏进程运行中</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGamePathNotValid" xml:space="preserve">
|
||||
<value>请选择游戏路径</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
|
||||
<value>下载游戏资源索引失败: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
|
||||
<value>游戏进程已退出</value>
|
||||
</data>
|
||||
@@ -1175,6 +1187,12 @@
|
||||
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
|
||||
<value>实时便笺 Webhook Url</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
|
||||
<value>解除限制后需要使用其他工具恢复限制</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
|
||||
<value>是否解除 Loopback 限制</value>
|
||||
</data>
|
||||
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
|
||||
<value>기원 기록 가져오기</value>
|
||||
</data>
|
||||
@@ -1271,6 +1289,12 @@
|
||||
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
||||
<value>使用米游社扫描二维码</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
|
||||
<value>为防止你在无意间启用,请输入正在启用的功能开关的<b>标题名称</b></value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
|
||||
<value>你正在启用一个危险功能</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>이 작업은 되돌릴 수 없으며, 모든 사용자 로그인 상태가 해제됩니다</value>
|
||||
</data>
|
||||
@@ -1292,6 +1316,9 @@
|
||||
<data name="ViewDialogUserTitle" xml:space="preserve">
|
||||
<value>쿠키 설정</value>
|
||||
</data>
|
||||
<data name="ViewFeedbackHeader" xml:space="preserve">
|
||||
<value>反馈中心</value>
|
||||
</data>
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>기원 기록</value>
|
||||
</data>
|
||||
@@ -1430,6 +1457,9 @@
|
||||
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
|
||||
<value>HoYoLab 계정은 실시간 메모 확인 기능을 지원하지 않습니다</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteModifyTaskFail" xml:space="preserve">
|
||||
<value>예약된 작업을 수정하지 못했습니다</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRefreshTime30" xml:space="preserve">
|
||||
<value>30분 | 3.75 레진</value>
|
||||
</data>
|
||||
@@ -1445,9 +1475,6 @@
|
||||
<data name="ViewModelDailyNoteRefreshTime8" xml:space="preserve">
|
||||
<value>8분 | 1 레진</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRegisterTaskFail" xml:space="preserve">
|
||||
<value>작업 등록에 실패했습니다. 관리자 모드로 실행하세요</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteRequestProgressTitle" xml:space="preserve">
|
||||
<value>正在获取实时便笺信息,请稍候</value>
|
||||
</data>
|
||||
@@ -1541,6 +1568,9 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>서버 변경 실패</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>识别显示器</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
|
||||
<value>无法读取游戏配置文件: {0},可能是文件不存在或权限不足</value>
|
||||
</data>
|
||||
@@ -1574,6 +1604,12 @@
|
||||
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
|
||||
<value>创建桌面快捷方式失败</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
|
||||
<value>后续转换会重新下载所需的文件,确定要删除吗?</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
||||
<value>删除转换服务器游戏客户端缓存</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
||||
<value>已使用磁盘空间:{0}</value>
|
||||
</data>
|
||||
@@ -1698,7 +1734,7 @@
|
||||
<value>캐릭터 특성 정보 동기화</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
|
||||
<value>HoYoLAB에서 내 캐릭터 동기화</value>
|
||||
<value>从米游社原神战绩同步</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
|
||||
<value>캐릭터 특성을 제외한 대부분의 정보 동기화</value>
|
||||
@@ -1823,6 +1859,48 @@
|
||||
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
|
||||
<value>현재 사용자와 캐릭터 확인</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackAutoSuggestBoxPlaceholder" xml:space="preserve">
|
||||
<value>搜索问题与建议</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedBackBasicInformation" xml:space="preserve">
|
||||
<value>基本信息</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
|
||||
<value>常用链接</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
|
||||
<value>当前代理</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
|
||||
<value>无代理</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
|
||||
<value>已解除</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
|
||||
<value>解除 Loopback 限制</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
|
||||
<value>与我们密切联系</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackFeatureGuideHeader" xml:space="preserve">
|
||||
<value>功能指南</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackGithubIssuesDescription" xml:space="preserve">
|
||||
<value>我们总是优先处理 GitHub 上的问题</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackRoadmapDescription" xml:space="preserve">
|
||||
<value>开发路线规划</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackSearchResultPlaceholderTitle" xml:space="preserve">
|
||||
<value>暂无搜索结果</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusDescription" xml:space="preserve">
|
||||
<value>胡桃服务可用性监控</value>
|
||||
</data>
|
||||
<data name="ViewPageFeedbackServerStatusHeader" xml:space="preserve">
|
||||
<value>胡桃服务</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
|
||||
<value>전체 동기화</value>
|
||||
</data>
|
||||
@@ -2142,7 +2220,7 @@
|
||||
<value>리소스 다운로드</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
|
||||
<value>클라이언트</value>
|
||||
<value>完整包</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>사전 다운로드</value>
|
||||
@@ -2192,6 +2270,12 @@
|
||||
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
|
||||
<value>활성화</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
|
||||
<value>充分利用支持高动态范围的显示器获得更亮、更生动、更精细的画面</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
|
||||
<value>Windows HDR</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>HoYoLab Uid를 입력하세요</value>
|
||||
</data>
|
||||
@@ -2564,9 +2648,18 @@
|
||||
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
|
||||
<value>调试控制台</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
|
||||
<value>在启动游戏中转换服务器后会产生对应的游戏客户端文件用作缓存</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
|
||||
<value>删除转换服务器缓存</value>
|
||||
</data>
|
||||
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
|
||||
<value>打开文件夹</value>
|
||||
</data>
|
||||
<data name="ViewSettingHeader" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="ViewSpiralAbyssAvatarAppearanceRankDescription" xml:space="preserve">
|
||||
<value>角色出场率 = 本层上阵该角色次数(层内重复出现只记一次)/ 深渊记录总数</value>
|
||||
</data>
|
||||
@@ -2747,6 +2840,9 @@
|
||||
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
|
||||
<value>{0}시간 후 종료</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
|
||||
<value>打开剪贴板失败</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已复制到剪贴板</value>
|
||||
</data>
|
||||
@@ -2847,7 +2943,7 @@
|
||||
<value>{0}초</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
|
||||
<value>인증에 실패했습니다. 수동으로 인증하거나 'HoYoLAB-전적-실시간 메모' 페이지에서 확인하시기 바랍니다.</value>
|
||||
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩-实时便笺」页面查看</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
|
||||
<value>错误的 UID 格式</value>
|
||||
@@ -2916,7 +3012,7 @@
|
||||
<value>胡桃服务维护中</value>
|
||||
</data>
|
||||
<data name="WebIndexOrSpiralAbyssVerificationFailed" xml:space="preserve">
|
||||
<value>验证失败,请手动验证或前往「米游社-我的角色」页面查看</value>
|
||||
<value>验证失败,请手动验证或前往「米游社-旅行工具-原神战绩」页面查看</value>
|
||||
</data>
|
||||
<data name="WebResponseFormat" xml:space="preserve">
|
||||
<value>상태:{0} | 정보:{1}</value>
|
||||
@@ -2927,4 +3023,7 @@
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{0}] 中的 [{1}] 网络请求异常,请稍后再试</value>
|
||||
</data>
|
||||
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
|
||||
<value>显示器编号</value>
|
||||
</data>
|
||||
</root>
|
||||
3029
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.pt.resx
Normal file
3029
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.pt.resx
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user