Compare commits

..

1 Commits

Author SHA1 Message Date
qhy040404
a02162f76d refresh data folder size after deleting server cache 2024-01-05 23:30:16 +08:00
320 changed files with 2940 additions and 15749 deletions

View File

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

View File

@@ -51,7 +51,7 @@ body:
description: |
In Snap Hutao's settings page, you can find and copy your device ID
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
If your program cannot startup, please download and run [Diagnosis 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 [this 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 [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.
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.
validations:
required: true

View File

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

View File

@@ -44,7 +44,9 @@ Install with Snap Hutao MSIX package, can be installed with Windows built-in App
### 特定的原神项目 / Specific Genshin-related Projects
* [Scighost/Starward](https://github.com/Scighost/Starward)
* [biuuu/genshin-wish-export](https://github.com/biuuu/genshin-wish-export)
* [xunkong/xunkong](https://github.com/xunkong/xunkong)
* [YuehaiTeam/cocogoat](https://github.com/YuehaiTeam/cocogoat)
### 使用的技术栈 / Tech Stack
@@ -55,6 +57,7 @@ Install with Snap Hutao MSIX package, can be installed with Windows built-in App
* [dotnet/efcore](https://github.com/dotnet/efcore)
* [dotnet/runtime](https://github.com/dotnet/runtime)
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)
* [microsoft/CsWin32](https://github.com/microsoft/CsWin32)
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)

View File

@@ -124,6 +124,9 @@ dotnet_diagnostic.SA1623.severity = none
# SA1636: File header copyright text should match
dotnet_diagnostic.SA1636.severity = none
# SA1414: Tuple types in signatures should have element names
dotnet_diagnostic.SA1414.severity = none
# SA0001: XML comment analysis disabled
dotnet_diagnostic.SA0001.severity = none
csharp_style_prefer_parameter_null_checking = true:suggestion
@@ -322,6 +325,7 @@ dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
csharp_style_prefer_primary_constructors = true:suggestion
dotnet_diagnostic.SA1010.severity = none
[*.vb]
#### 命名样式 ####

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,8 @@ 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
@@ -65,16 +67,32 @@ 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_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"]}
RESX_ShowErrorsInErrorList = False
RESX_SortFileContentOnSave = True
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
RESX_NeutralResourcesLanguage = zh-CN
RESX_AutoApplyExistingTranslations = False
EndGlobalSection
EndGlobal

View File

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

View File

@@ -6,7 +6,6 @@ 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;
@@ -37,6 +36,8 @@ public sealed partial class App : Application
----------------------------------------------------------------
""";
private const string AppInstanceKey = "main";
private readonly IServiceProvider serviceProvider;
private readonly IActivation activation;
private readonly ILogger<App> logger;
@@ -49,6 +50,7 @@ public sealed partial class App : Application
{
// Load app resource
InitializeComponent();
activation = serviceProvider.GetRequiredService<IActivation>();
logger = serviceProvider.GetRequiredService<ILogger<App>>();
serviceProvider.GetRequiredService<ExceptionRecorder>().Record(this);
@@ -62,21 +64,25 @@ public sealed partial class App : Application
try
{
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance firstInstance = AppInstance.FindOrRegisterForKey(AppInstanceKey);
if (serviceProvider.GetRequiredService<PrivateNamedPipeClient>().TryRedirectActivationTo(activatedEventArgs))
if (firstInstance.IsCurrent)
{
Exit();
return;
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();
}
logger.LogInformation(ConsoleBanner);
LogDiagnosticInformation();
// manually invoke
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
activation.Initialize();
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
}
catch
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ 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;
@@ -24,11 +23,8 @@ namespace Snap.Hutao.Control.Text;
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
internal sealed partial class DescriptionTextBlock : ContentControl
{
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 ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".Length;
private static readonly int ItalicTagFullLength = "<i></i>".Length;
private static readonly int ItalicTagLeftLength = "<i>".Length;
@@ -55,16 +51,9 @@ internal sealed partial class DescriptionTextBlock : ContentControl
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
ReadOnlySpan<char> description = SpecialNameHandler.Handle((string)e.NewValue);
ReadOnlySpan<char> description = (string)e.NewValue;
try
{
UpdateDescription(textBlock, description);
}
catch (Exception ex)
{
_ = ex;
}
UpdateDescription(textBlock, description);
}
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -92,32 +81,13 @@ internal sealed partial class DescriptionTextBlock : ContentControl
// color tag
else if (description[i..].StartsWith("<c"))
{
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);
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);
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;
}
}
i += length + ColorTagFullLength;
last = i;
}
// italic

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,6 @@
<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>
@@ -28,7 +27,6 @@
<!-- EmotionIcon -->
<x:String x:Key="UI_EmotionIcon25">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon25.png</x:String>
<x:String x:Key="UI_EmotionIcon52">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon52.png</x:String>
<x:String x:Key="UI_EmotionIcon71">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon71.png</x:String>
<x:String x:Key="UI_EmotionIcon250">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.png</x:String>
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>

View File

@@ -2,12 +2,9 @@
// 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;
@@ -34,7 +31,7 @@ internal static class DependencyInjection
.AddJsonOptions()
.AddDatabase()
.AddInjections()
.AddAllHttpClients()
.AddHttpClients()
// Discrete services
.AddSingleton<IMessenger, WeakReferenceMessenger>()
@@ -51,10 +48,10 @@ internal static class DependencyInjection
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InitializeCulture(this IServiceProvider serviceProvider)
{
CultureOptions cultureOptions = serviceProvider.GetRequiredService<CultureOptions>();
cultureOptions.SystemCulture = CultureInfo.CurrentCulture;
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
CultureInfo cultureInfo = cultureOptions.CurrentCulture;
CultureInfo cultureInfo = appOptions.CurrentCulture;
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;

View File

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

View File

@@ -1,7 +1,6 @@
// 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;
@@ -15,26 +14,12 @@ internal static partial class IocHttpClientConfiguration
{
private const string ApplicationJson = "application/json";
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)]
/// <summary>
/// 添加 <see cref="HttpClient"/>
/// 此方法将会自动生成
/// </summary>
/// <param name="services">集合</param>
/// <returns>可继续操作的集合</returns>
public static partial IServiceCollection AddHttpClients(this IServiceCollection services);
/// <summary>
@@ -44,10 +29,10 @@ internal static partial class IocHttpClientConfiguration
/// <param name="client">配置后的客户端</param>
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
{
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
}
/// <summary>
@@ -104,7 +89,6 @@ internal static partial class IocHttpClientConfiguration
/// HoYoLAB web
/// </summary>
/// <param name="client">配置后的客户端</param>
[SuppressMessage("", "IDE0051")]
private static void XRpc4Configuration(HttpClient client)
{
client.Timeout = Timeout.InfiniteTimeSpan;

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,6 @@ 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)
@@ -29,7 +28,6 @@ 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)
@@ -58,11 +56,7 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
public Task CopyAsync(IProgress<TStatus> progress, CancellationToken token = default)
{
ShardProgress shardProgress = new(progress, statusFactory, contentLength);
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = maxDegreeOfParallelism,
};
return Parallel.ForEachAsync(shards, parallelOptions, (shard, token) => CopyShardAsync(shard, shardProgress, token));
return Parallel.ForEachAsync(shards, token, (shard, token) => CopyShardAsync(shard, shardProgress, token));
async ValueTask CopyShardAsync(Shard shard, IProgress<ShardStatus> progress, CancellationToken token)
{

View File

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

View File

@@ -11,14 +11,11 @@ namespace Snap.Hutao.Core.IO.Ini;
[HighQuality]
internal static class IniSerializer
{
public static List<IniElement> DeserializeFromFile(string filePath)
{
using (FileStream readStream = File.OpenRead(filePath))
{
return Deserialize(readStream);
}
}
/// <summary>
/// 反序列化
/// </summary>
/// <param name="fileStream">文件流</param>
/// <returns>Ini 元素集合</returns>
public static List<IniElement> Deserialize(FileStream fileStream)
{
List<IniElement> results = [];
@@ -53,14 +50,11 @@ internal static class IniSerializer
return results;
}
public static void SerializeToFile(string filePath, IEnumerable<IniElement> elements)
{
using (FileStream writeStream = File.Create(filePath))
{
Serialize(writeStream, elements);
}
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="fileStream">写入的流</param>
/// <param name="elements">元素</param>
public static void Serialize(FileStream fileStream, IEnumerable<IniElement> elements)
{
using (StreamWriter writer = new(fileStream))

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Win32.Foundation;
using Windows.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
: WindowNative.GetWindowHandle(reference.Window);
: (HWND)WindowNative.GetWindowHandle(reference.Window);
}
}

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
namespace Snap.Hutao.Core.LifeCycle;
/// <summary>
@@ -8,7 +10,24 @@ namespace Snap.Hutao.Core.LifeCycle;
/// </summary>
internal interface IActivation
{
void Activate(HutaoActivationArguments args);
/// <summary>
/// 响应激活事件
/// 激活事件一般不会在UI线程上触发
/// </summary>
/// <param name="sender">发送方</param>
/// <param name="args">激活参数</param>
void Activate(object? sender, AppActivationArguments args);
void Initialize();
/// <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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,99 +14,113 @@ namespace Snap.Hutao.Core;
[Injection(InjectAs.Singleton)]
internal sealed class RuntimeOptions
{
private readonly Lazy<(Version Version, string UserAgent)> lazyVersionAndUserAgent = new(() =>
{
Version version = Package.Current.Id.Version.ToVersion();
return (version, $"Snap Hutao/{version}");
});
private readonly bool isWebView2Supported;
private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected;
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);
private bool? isElevated;
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 => lazyVersionAndUserAgent.Value.Version; }
public Version Version { get; }
public string UserAgent { get => lazyVersionAndUserAgent.Value.UserAgent; }
public string UserAgent { get; }
public string InstalledLocation { get => lazyInstalledLocation.Value; }
public string InstalledLocation { get; }
public string DataFolder { get => lazyDataFolder.Value; }
public string DataFolder { get; }
public string LocalCache { get => lazyLocalCache.Value; }
public string LocalCache { get; }
public string FamilyName { get => lazyFamilyName.Value; }
public string FamilyName { get; }
public string DeviceId { get => lazyDeviceId.Value; }
public string DeviceId { get; }
public string WebView2Version { get => lazyWebViewEnvironment.Value.Version; }
public string WebView2Version { get => webView2Version; }
public bool IsWebView2Supported { get => lazyWebViewEnvironment.Value.Supported; }
public bool IsWebView2Supported { get => isWebView2Supported; }
public bool IsElevated { get => lazyElevated.Value; }
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 DateTimeOffset AppLaunchTime { get; }
}

View File

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

View File

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

View File

@@ -1,14 +1,15 @@
// 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 static Snap.Hutao.Win32.Macros;
using static Snap.Hutao.Win32.Ole32;
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;
namespace Snap.Hutao.Core.Shell;
@@ -33,44 +34,31 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
return false;
}
return UnsafeTryCreateDesktopShoutcutForElevatedLaunch(targetLogoPath);
}
HRESULT result = CoCreateInstance<ShellLink, IShellLinkW>(null, CLSCTX.CLSCTX_INPROC_SERVER, out IShellLinkW shellLink);
Marshal.ThrowExceptionForHR(result);
private unsafe bool UnsafeTryCreateDesktopShoutcutForElevatedLaunch(string targetLogoPath)
{
bool result = false;
shellLink.SetPath($"shell:AppsFolder\\{runtimeOptions.FamilyName}!App");
shellLink.SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL);
shellLink.SetIconLocation(targetLogoPath, 0);
// 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))
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
{
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();
persistFile.Save(target, false);
}
catch (UnauthorizedAccessException)
{
return false;
}
return result;
return true;
}
}

View File

@@ -6,7 +6,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using Windows.Graphics;
namespace Snap.Hutao.Win32;
namespace Snap.Hutao.Core;
/// <summary>
/// 结构体封送
@@ -69,9 +69,4 @@ internal static class StructMarshal
{
return new(point.X, point.Y, size.Width, size.Height);
}
public static SizeInt32 SizeInt32(RectInt32 rect)
{
return new(rect.Width, rect.Height);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,15 +9,13 @@ 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 static Snap.Hutao.Win32.DwmApi;
using static Snap.Hutao.Win32.User32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Core.Windowing;
@@ -55,10 +53,10 @@ internal sealed class WindowController
private void InitializeCore()
{
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
window.AppWindow.Title = SH.FormatAppNameAndVersion(runtimeOptions.Version);
window.AppWindow.SetIcon(Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico"));
window.AppWindow.Title = SH.FormatAppNameAndVersion(hutaoOptions.Version);
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
ExtendsContentIntoTitleBar();
RecoverOrInitWindowSize();
@@ -106,11 +104,11 @@ internal sealed class WindowController
return;
}
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create();
WINDOWPLACEMENT windowPlacement = Win32.StructMarshal.WINDOWPLACEMENT();
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));
@@ -159,7 +157,6 @@ 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(),
@@ -196,7 +193,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, ref isDarkMode);
DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &isDarkMode, unchecked((uint)sizeof(BOOL)));
}
private void UpdateDragRectangles()

View File

@@ -2,11 +2,7 @@
// 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;
@@ -20,12 +16,4 @@ internal static class WindowExtension
WindowController windowController = new(window, window.WindowOptions, serviceProvider);
WindowControllers.Add(window, windowController);
}
public static void SetLayeredWindow(this Window window)
{
HWND hwnd = (HWND)WindowNative.GetWindowHandle(window);
nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED;
SetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style);
}
}

View File

@@ -4,10 +4,10 @@
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Snap.Hutao.Win32.Foundation;
using Windows.Graphics;
using Windows.Win32.Foundation;
using WinRT.Interop;
using static Snap.Hutao.Win32.User32;
using static Windows.Win32.PInvoke;
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 = WindowNative.GetWindowHandle(window);
Hwnd = (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, default);
uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd, default);
uint threadIdHwnd = GetWindowThreadProcessId(Hwnd);
uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd);
if (threadIdHwnd != threadIdFgHwnd)
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
<Window
x:Class="Snap.Hutao.IdentifyMonitorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
mc:Ignorable="d">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="3">
<TextBlock Text="{shcm:ResourceString Name=WindowIdentifyMonitorHeader}"/>
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind Monitor}"
TextAlignment="Center"/>
</StackPanel>
</Window>

View File

@@ -1,30 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Snap.Hutao.Win32;
using Windows.Graphics;
namespace Snap.Hutao;
internal sealed partial class IdentifyMonitorWindow : Window
{
public IdentifyMonitorWindow(DisplayArea displayArea, int index)
{
InitializeComponent();
Monitor = $"{displayArea.DisplayId.Value:X8}:{index}";
OverlappedPresenter presenter = OverlappedPresenter.Create();
presenter.SetBorderAndTitleBar(false, false);
presenter.IsAlwaysOnTop = true;
presenter.IsResizable = false;
AppWindow.SetPresenter(presenter);
PointInt32 point = new(40, 32);
SizeInt32 size = StructMarshal.SizeInt32(displayArea.WorkArea).Scale(0.1);
AppWindow.MoveAndResize(StructMarshal.RectInt32(point, size), displayArea);
}
public string Monitor { get; private set; }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,7 @@
// 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;
@@ -52,20 +50,8 @@ internal sealed partial class DescriptionsParametersDescriptor : ValueConverter<
{
if (desc.AsSpan().TrySplitIntoTwo('|', out ReadOnlySpan<char> description, out ReadOnlySpan<char> format))
{
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 });
}
string resultFormatted = ParamRegex().Replace(format.ToString(), match => ReplaceParamInMatch(match, paramList));
results.Add(new ParameterDescription { Description = description.ToString(), Parameter = resultFormatted });
}
else
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -247,7 +247,7 @@
<value>Not refreshed</value>
</data>
<data name="ModelEntityDailyNoteRefreshTimeFormat" xml:space="preserve">
<value>Refreshed at {0:MM.dd HH:mm:ss}</value>
<value>Refresh 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>Battle Chronicle: Not Refreshed</value>
<value>My Characters: N/A</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>Battle Chronicle: {0:MM-dd HH:mm}</value>
<value>My Characters: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>Enka: N/A</value>
@@ -870,23 +870,11 @@
<value>No writing permission in file system, unable to start the server conversion.</value>
</data>
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
<value>Download game resource index</value>
<value>Querying Game Resource Information</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>
@@ -1170,7 +1158,7 @@
<value>Realm Currency Alarm Threshold</value>
</data>
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
<value>Original Resin Alarm Threshold</value>
<value>Realm Currency Alarm Threshold</value>
</data>
<data name="ViewDialogDailyNoteNotificationShowInHomeWidget" xml:space="preserve">
<value>Show widget on Home page</value>
@@ -1187,12 +1175,6 @@
<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>
@@ -1289,12 +1271,6 @@
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
<value>Scan the QR code with MiHoYo BBS App</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>To avoid enable in a mistake, please input &lt;b&gt;title name&lt;/b&gt; of feature you are enabling</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>You are Enabling a Dangerous Feature</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>This action is irreversible, and all user login status will be lost</value>
</data>
@@ -1316,9 +1292,6 @@
<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>
@@ -1395,7 +1368,7 @@
<value>This operation is irreversible. The achievement archive will be lost.</value>
</data>
<data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve">
<value>Are you sure to delete archive {0}?</value>
<value>Are you sure you want to delete archive {0}?</value>
</data>
<data name="ViewModelAchievementUIAFExportPickerTitle" xml:space="preserve">
<value>Export UIAF Json file to the selected path</value>
@@ -1457,9 +1430,6 @@
<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>
@@ -1475,11 +1445,14 @@
<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 path</value>
<value>Successfully saved to selected patch</value>
</data>
<data name="ViewModelExportSuccessTitle" xml:space="preserve">
<value>Export successfully</value>
@@ -1518,7 +1491,7 @@
<value>Failed to fetch wish history</value>
</data>
<data name="ViewModelGachaLogRefreshOperationCancel" xml:space="preserve">
<value>Wish history refresh was canceled unexpectedly</value>
<value>Wish history refresh is canceled unexpectedly</value>
</data>
<data name="ViewModelGachaLogRemoveArchiveDescription" xml:space="preserve">
<value>This operation is irreversible. The wish history archive will be lost.</value>
@@ -1568,9 +1541,6 @@
<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>
@@ -1604,12 +1574,6 @@
<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>
@@ -1662,7 +1626,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>
@@ -1683,7 +1647,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>
@@ -1707,7 +1671,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>
@@ -1734,7 +1698,7 @@
<value>Sync character talents data</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>Sync from MiHoYo BBS Battle Chronicle</value>
<value>Sync from MiHoYo BBS My Characters</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>Sync most data other than character talent</value>
@@ -1758,7 +1722,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>
@@ -1859,48 +1823,6 @@
<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>
@@ -1953,7 +1875,7 @@
<value>Input</value>
</data>
<data name="ViewPageGachaLogRecoverFromHutaoCloudDescription" xml:space="preserve">
<value>Recover Wish Records from Snap Hutao Cloud</value>
<value>Recover Wish Record from Snap Hutao Cloud</value>
</data>
<data name="ViewPageGachaLogRefresh" xml:space="preserve">
<value>Refresh</value>
@@ -2133,10 +2055,10 @@
<value>Borderless</value>
</data>
<data name="ViewPageLaunchGameAppearanceCloudThirdPartyMobileDescription" xml:space="preserve">
<value>Enable touchscreen layout, but the keyboard &amp; mouse are no longer usable</value>
<value>Enable touchscreen layout, but the keyboard &amp; mouse are no longer usable.</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveDescription" xml:space="preserve">
<value>Incompatible with embedded browsers; operations like switching window may cause the game to crash</value>
<value>Incompatible with embedded browsers; operations like switching window may cause the game to crash.</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveHeader" xml:space="preserve">
<value>Exclusive Fullscreen</value>
@@ -2166,7 +2088,7 @@
<value>Modify its default behavior at game startup</value>
</data>
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Launch Parameters</value>
<value>Start-up Arguments</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>General</value>
@@ -2220,7 +2142,7 @@
<value>Resource Download</value>
</data>
<data name="ViewPageLaunchGameResourceLatestHeader" xml:space="preserve">
<value>Full Package</value>
<value>Client</value>
</data>
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Pre-download</value>
@@ -2270,12 +2192,6 @@
<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>
@@ -2289,7 +2205,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>
@@ -2493,7 +2409,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>Advanced Features</value>
<value>Enable Advanced Features</value>
</data>
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
<value>Change Auto Click Shortcut</value>
@@ -2604,7 +2520,7 @@
<value>Quotes</value>
</data>
<data name="ViewPageWiKiAvatarSpecialFoodTitle" xml:space="preserve">
<value>Special Dish</value>
<value>Special Food</value>
</data>
<data name="ViewPageWiKiAvatarStoriesHeader" xml:space="preserve">
<value>Stories</value>
@@ -2648,18 +2564,9 @@
<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>
@@ -2754,7 +2661,7 @@
<value>Web Login</value>
</data>
<data name="ViewUserCookieOperationLoginQRCodeAction" xml:space="preserve">
<value>Login via QR code</value>
<value>Login via scanning QR code</value>
</data>
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
<value>Input Manually</value>
@@ -2840,9 +2747,6 @@
<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>
@@ -2907,10 +2811,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>
@@ -2943,7 +2847,7 @@
<value>{0} Seconds</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>Verification failed. Please check MiHoYo BBS - Toolbox - Realtime Notes manually</value>
<value>Verification failed. Please check MiHoYo BBS - My Character - Realtime Notes manually</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>Wrong UID format</value>
@@ -3012,7 +2916,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 - Battle Chronicle page</value>
<value>Verification failed. Please verify manually or check MiHoYo BBS - My Characters page</value>
</data>
<data name="WebResponseFormat" xml:space="preserve">
<value>Return Code: {0} | Message: {1}</value>
@@ -3023,7 +2927,4 @@
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
<value>[{1}] network request exception in [{0}] please try again later</value>
</data>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>Monitor ID</value>
</data>
</root>
</root>

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