Merge pull request #1286 from DGP-Studio/feat/feedback_network

This commit is contained in:
DismissedLight
2024-01-27 13:02:28 +08:00
committed by GitHub
12 changed files with 292 additions and 102 deletions

View File

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

View File

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

View File

@@ -0,0 +1,85 @@
// 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 SID hutaoContainerSID;
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))
{
hutaoContainerSID = *pContainer->appContainerSid;
break;
}
}
}
finally
{
uint retVal = NetworkIsolationFreeAppContainers(pContainers);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(*(WIN32_ERROR*)&retVal));
}
WIN32_ERROR error2 = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error2));
fixed (SID* phutaoContainerSID = &hutaoContainerSID)
{
for (uint i = 0; i < accCount; i++)
{
if (EqualSid(phutaoContainerSID, (pSids + i)->Sid))
{
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));
}
fixed (SID* phutaoContainerSID = &hutaoContainerSID)
{
SID_AND_ATTRIBUTES sidAndAttributes = default;
sidAndAttributes.Sid = phutaoContainerSID;
sids.Add(sidAndAttributes);
IsLoopbackEnabled = NetworkIsolationSetAppContainerConfig(CollectionsMarshal.AsSpan(sids)) is WIN32_ERROR.ERROR_SUCCESS;
}
}
}

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.
-->
@@ -1862,6 +1862,12 @@
<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="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>Keep in touch with us</value>
</data>
@@ -3008,4 +3014,4 @@
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>Monitor ID</value>
</data>
</root>
</root>

View File

@@ -1187,6 +1187,12 @@
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
<value>实时便笺 Webhook Url</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制后需要使用其他工具恢复限制</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>是否解除 Loopback 限制</value>
</data>
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
<value>导入祈愿记录</value>
</data>
@@ -1862,6 +1868,18 @@
<data name="ViewPageFeedbackCommonLinksHeader" xml:space="preserve">
<value>常用链接</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>当前代理</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>无代理</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>已解除</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>解除 Loopback 限制</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>与我们密切联系</value>
</data>

View File

@@ -3,7 +3,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clw="using:CommunityToolkit.Labs.WinUI"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -28,6 +29,15 @@
<SolidColorBrush x:Key="SettingsCardBackgroundDisabled" Color="Transparent"/>
<SolidColorBrush x:Key="SettingsCardBackgroundPointerOver" Color="Transparent"/>
<SolidColorBrush x:Key="SettingsCardBackgroundPressed" Color="Transparent"/>
<cwconv:BoolToObjectConverter x:Key="BoolToLoopbackDescriptionControlConverter">
<cwconv:BoolToObjectConverter.TrueValue>
<TextBlock Text="{shcm:ResourceString Name=ViewPageFeedbackEnableLoopbackEnabledDescription}"/>
</cwconv:BoolToObjectConverter.TrueValue>
<cwconv:BoolToObjectConverter.FalseValue>
<HyperlinkButton Content="{shcm:ResourceString Name=ViewPageAnnouncementViewDetails}" NavigateUri="{StaticResource DocumentLink_Loopback}"/>
</cwconv:BoolToObjectConverter.FalseValue>
</cwconv:BoolToObjectConverter>
</Page.Resources>
<Grid>
@@ -41,152 +51,162 @@
<ScrollViewer>
<StackPanel Margin="16" Spacing="3">
<Border Style="{ThemeResource AcrylicBorderlessBorderCardStyle}">
<cwc:SettingsExpander
<cwcont:SettingsExpander
Description="{Binding RuntimeOptions.Version}"
Header="{shcm:ResourceString Name=AppName}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"
IsExpanded="True">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE8C8;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCopyDeviceIdAction}"
Command="{Binding CopyDeviceIdCommand}"
Description="{Binding RuntimeOptions.DeviceId}"
Header="{shcm:ResourceString Name=ViewPageSettingDeviceIdHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard Description="{Binding IPInformation}" Header="{shcm:ResourceString Name=ViewPageSettingDeviceIpHeader}"/>
<cwc:SettingsCard Description="{Binding RuntimeOptions.WebView2Version}" Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwcont:SettingsCard Description="{Binding IPInformation}" Header="{shcm:ResourceString Name=ViewPageSettingDeviceIpHeader}"/>
<cwcont:SettingsCard Description="{Binding DynamicHttpProxy.CurrentProxy}" Header="{shcm:ResourceString Name=ViewPageFeedbackCurrentProxyHeader}"/>
<cwcont:SettingsCard
Command="{Binding EnableLoopbackCommand}"
Header="{shcm:ResourceString Name=ViewPageFeedbackEnableLoopbackHeader}"
IsClickEnabled="True"
IsEnabled="{Binding LoopbackManager.IsLoopbackEnabled, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<cwcont:SettingsCard.Description>
<UserControl Content="{Binding LoopbackManager.IsLoopbackEnabled, Converter={StaticResource BoolToLoopbackDescriptionControlConverter}, Mode=OneWay}"/>
</cwcont:SettingsCard.Description>
</cwcont:SettingsCard>
<cwcont:SettingsCard Description="{Binding RuntimeOptions.WebView2Version}" Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
</Border>
<Border Style="{ThemeResource AcrylicBorderlessBorderCardStyle}">
<cwc:SettingsExpander
<cwcont:SettingsExpander
Description="{shcm:ResourceString Name=ViewPageFeedbackEngageWithUsDescription}"
Header="{shcm:ResourceString Name=ViewPageFeedbackCommonLinksHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE71B;}"
IsExpanded="True">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://github.com/DGP-Studio/Snap.Hutao/issues/new/choose"
Description="{shcm:ResourceString Name=ViewPageFeedbackGithubIssuesDescription}"
Header="GitHub Issues"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://github.com/orgs/DGP-Studio/projects/2"
Description="{shcm:ResourceString Name=ViewPageFeedbackRoadmapDescription}"
Header="GitHub Projects"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://status.hut.ao"
Description="{shcm:ResourceString Name=ViewPageFeedbackServerStatusDescription}"
Header="{shcm:ResourceString Name=ViewPageFeedbackServerStatusHeader}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
</Border>
<Border Style="{ThemeResource AcrylicBorderlessBorderCardStyle}">
<cwc:SettingsExpander
<cwcont:SettingsExpander
Header="{shcm:ResourceString Name=ViewPageFeedbackFeatureGuideHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xF8A5;}"
IsExpanded="True">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/dashboard.html"
Header="{shcm:ResourceString Name=ViewAnnouncementHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Announcement.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/game-launcher.html"
Header="{shcm:ResourceString Name=ViewLaunchGameHeader}"
IsClickEnabled="True">
<cwc:SettingsCard.HeaderIcon>
<cwcont:SettingsCard.HeaderIcon>
<!-- This icon is not a square -->
<BitmapIcon
Width="24"
Height="24"
ShowAsMonochrome="False"
UriSource="ms-appx:///Resource/Navigation/LaunchGame.png"/>
</cwc:SettingsCard.HeaderIcon>
</cwc:SettingsCard>
<cwc:SettingsCard
</cwcont:SettingsCard.HeaderIcon>
</cwcont:SettingsCard>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/wish-export.html"
Header="{shcm:ResourceString Name=ViewGachaLogHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/GachaLog.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/achievements.html"
Header="{shcm:ResourceString Name=ViewAchievementHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Achievement.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/real-time-notes.html"
Header="{shcm:ResourceString Name=ViewDailyNoteHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/DailyNote.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/character-data.html"
Header="{shcm:ResourceString Name=ViewAvatarPropertyHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/AvatarProperty.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/hutao-API.html"
Header="{shcm:ResourceString Name=ViewSpiralAbyssHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/SpiralAbyss.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/develop-plan.html"
Header="{shcm:ResourceString Name=ViewCultivationHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Cultivation.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/character-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiAvatarHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiAvatar.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/weapon-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiWeaponHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiWeapon.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/monster-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiMonsterHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiMonster.png}"
IsClickEnabled="True"/>
<cwc:SettingsCard
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/hutao-settings.html"
Header="{shcm:ResourceString Name=ViewSettingHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE713;}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
</Border>
</StackPanel>
</ScrollViewer>

View File

@@ -1,8 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Core.IO.Http.DynamicProxy;
using Snap.Hutao.Core.IO.Http.Loopback;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Service;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Web.Hutao;
@@ -19,7 +23,10 @@ internal sealed partial class FeedbackViewModel : Abstraction.ViewModel
{
private readonly HutaoInfrastructureClient hutaoInfrastructureClient;
private readonly HutaoDocumentationClient hutaoDocumentationClient;
private readonly IContentDialogFactory contentDialogFactory;
private readonly IClipboardProvider clipboardProvider;
private readonly DynamicHttpProxy dynamicHttpProxy;
private readonly LoopbackManager loopbackManager;
private readonly IInfoBarService infoBarService;
private readonly CultureOptions cultureOptions;
private readonly RuntimeOptions runtimeOptions;
@@ -31,6 +38,10 @@ internal sealed partial class FeedbackViewModel : Abstraction.ViewModel
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
public DynamicHttpProxy DynamicHttpProxy { get => dynamicHttpProxy; }
public LoopbackManager LoopbackManager { get => loopbackManager; }
public string? SearchText { get => searchText; set => SetProperty(ref searchText, value); }
public List<AlgoliaHit>? SearchResults { get => searchResults; set => SetProperty(ref searchResults, value); }
@@ -109,4 +120,18 @@ internal sealed partial class FeedbackViewModel : Abstraction.ViewModel
infoBarService.Error(ex);
}
}
[Command("EnableLoopbackCommand")]
private async Task EnableLoopbackAsync()
{
ContentDialogResult result = await contentDialogFactory
.CreateForConfirmCancelAsync(SH.ViewDialogFeedbackEnableLoopbackTitle, SH.ViewDialogFeedbackEnableLoopbackContent)
.ConfigureAwait(false);
if (result is ContentDialogResult.Primary)
{
await taskContext.SwitchToMainThreadAsync();
LoopbackManager.EnableLoopback();
}
}
}

View File

@@ -13,6 +13,10 @@ namespace Snap.Hutao.Win32;
[SuppressMessage("", "SYSLIB1054")]
internal static class AdvApi32
{
[DllImport("ADVAPI32.dll", ExactSpelling = true, SetLastError = true)]
[SupportedOSPlatform("windows5.1.2600")]
public static extern BOOL EqualSid(PSID pSid1, PSID pSid2);
[DllImport("ADVAPI32.dll", ExactSpelling = true)]
[SupportedOSPlatform("windows5.0")]
public static extern WIN32_ERROR RegCloseKey(HKEY hKey);

View File

@@ -20,13 +20,14 @@ internal static class ApiMsWinNetIsolation
public static unsafe extern uint NetworkIsolationEnumAppContainers(uint Flags, uint* pdwNumPublicAppCs, INET_FIREWALL_APP_CONTAINER** ppPublicAppCs);
[DebuggerStepThrough]
public static unsafe uint NetworkIsolationEnumAppContainers(NETISO_FLAG Flags, out uint dwNumPublicAppCs, out INET_FIREWALL_APP_CONTAINER* pPublicAppCs)
public static unsafe WIN32_ERROR NetworkIsolationEnumAppContainers(NETISO_FLAG Flags, out uint dwNumPublicAppCs, out INET_FIREWALL_APP_CONTAINER* pPublicAppCs)
{
fixed (uint* pdwNumPublicAppCs = &dwNumPublicAppCs)
{
fixed (INET_FIREWALL_APP_CONTAINER** ppPublicAppCs = &pPublicAppCs)
{
return NetworkIsolationEnumAppContainers((uint)Flags, pdwNumPublicAppCs, ppPublicAppCs);
uint retVal = NetworkIsolationEnumAppContainers((uint)Flags, pdwNumPublicAppCs, ppPublicAppCs);
return *(WIN32_ERROR*)&retVal;
}
}
}
@@ -36,11 +37,12 @@ internal static class ApiMsWinNetIsolation
public static unsafe extern uint NetworkIsolationFreeAppContainers(INET_FIREWALL_APP_CONTAINER* pPublicAppCs);
[DebuggerStepThrough]
public static unsafe uint NetworkIsolationFreeAppContainers(ref readonly INET_FIREWALL_APP_CONTAINER publicAppCs)
public static unsafe WIN32_ERROR NetworkIsolationFreeAppContainers(ref readonly INET_FIREWALL_APP_CONTAINER publicAppCs)
{
fixed (INET_FIREWALL_APP_CONTAINER* pPublicAppCs = &publicAppCs)
{
return NetworkIsolationFreeAppContainers(pPublicAppCs);
uint retVal = NetworkIsolationFreeAppContainers(pPublicAppCs);
return *(WIN32_ERROR*)&retVal;
}
}
@@ -49,13 +51,14 @@ internal static class ApiMsWinNetIsolation
public static unsafe extern uint NetworkIsolationGetAppContainerConfig(uint* pdwNumPublicAppCs, SID_AND_ATTRIBUTES** appContainerSids);
[DebuggerStepThrough]
public static unsafe uint NetworkIsolationGetAppContainerConfig(out uint dwNumPublicAppCs, out SID_AND_ATTRIBUTES* appContainerSids)
public static unsafe WIN32_ERROR NetworkIsolationGetAppContainerConfig(out uint dwNumPublicAppCs, out SID_AND_ATTRIBUTES* appContainerSids)
{
fixed (uint* pdwNumPublicAppCs = &dwNumPublicAppCs)
{
fixed (SID_AND_ATTRIBUTES** pAppContainerSids = &appContainerSids)
{
return NetworkIsolationGetAppContainerConfig(pdwNumPublicAppCs, pAppContainerSids);
uint retVal = NetworkIsolationGetAppContainerConfig(pdwNumPublicAppCs, pAppContainerSids);
return *(WIN32_ERROR*)&retVal;
}
}
}
@@ -65,11 +68,12 @@ internal static class ApiMsWinNetIsolation
public static unsafe extern uint NetworkIsolationSetAppContainerConfig(uint dwNumPublicAppCs, SID_AND_ATTRIBUTES* appContainerSids);
[DebuggerStepThrough]
public static unsafe uint NetworkIsolationSetAppContainerConfig(ReadOnlySpan<SID_AND_ATTRIBUTES> appContainerSids)
public static unsafe WIN32_ERROR NetworkIsolationSetAppContainerConfig(ReadOnlySpan<SID_AND_ATTRIBUTES> appContainerSids)
{
fixed (SID_AND_ATTRIBUTES* pAppContainerSids = appContainerSids)
{
return NetworkIsolationSetAppContainerConfig((uint)appContainerSids.Length, pAppContainerSids);
uint retVal = NetworkIsolationSetAppContainerConfig((uint)appContainerSids.Length, pAppContainerSids);
return *(WIN32_ERROR*)&retVal;
}
}
}

View File

@@ -3,8 +3,8 @@
namespace Snap.Hutao.Win32.Foundation;
internal unsafe struct FlexibleArray<T>
where T : unmanaged
internal unsafe struct FlexibleArray<TElement>
where TElement : unmanaged
{
public T* Reference;
public TElement* Reference;
}

View File

@@ -1,9 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Security;
namespace Snap.Hutao.Win32.Foundation;
internal struct PSID
{
public unsafe void* Value;
public static unsafe implicit operator PSID(SID* value) => *(PSID*)&value;
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
using Snap.Hutao.Win32.Foundation;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32.UI.Shell.Common;