diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml index a73bbbae..d6789e48 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml @@ -4,6 +4,7 @@ https://hut.ao https://hut.ao/features/mhy-account-switch.html https://translate.hut.ao + 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 https://github.com/HolographicHat/GetToken/releases/latest diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/DynamicProxy/DynamicHttpProxy.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/DynamicProxy/DynamicHttpProxy.cs index 3cee1312..73a854c9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/DynamicProxy/DynamicHttpProxy.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/DynamicProxy/DynamicHttpProxy.cs @@ -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 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(); } - /// - 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(); + 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; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Loopback/LoopbackManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Loopback/LoopbackManager.cs new file mode 100644 index 00000000..607349aa --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Http/Loopback/LoopbackManager.cs @@ -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(); + taskContext = serviceProvider.GetRequiredService(); + + 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 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 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; + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx index 321a9291..744206bf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx @@ -1,17 +1,17 @@  - @@ -1862,6 +1862,12 @@ Useful Links + + Current Proxy + + + No Proxy + Keep in touch with us @@ -3008,4 +3014,4 @@ Monitor ID - \ No newline at end of file + diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index b801ff1a..c2094ee5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -1187,6 +1187,12 @@ 实时便笺 Webhook Url + + 解除限制后需要使用其他工具恢复限制 + + + 是否解除 Loopback 限制 + 导入祈愿记录 @@ -1862,6 +1868,18 @@ 常用链接 + + 当前代理 + + + 无代理 + + + 已解除 + + + 解除 Loopback 限制 + 与我们密切联系 diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/FeedbackPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/FeedbackPage.xaml index 05d6783a..943f8172 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/FeedbackPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/FeedbackPage.xaml @@ -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 @@ + + + + + + + + + @@ -41,152 +51,162 @@ - - - + - - - - + + + + + + + + + + - - - + - - - - + + - - - + - - + - - - + + - - - - - - - - - - - + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Feedback/FeedbackViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Feedback/FeedbackViewModel.cs index aa5b5b65..e80b8edb 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Feedback/FeedbackViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Feedback/FeedbackViewModel.cs @@ -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? 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(); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs b/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs index de928361..d95aa0e4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/AdvApi32.cs @@ -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); diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/ApiMsWinNetIsolation.cs b/src/Snap.Hutao/Snap.Hutao/Win32/ApiMsWinNetIsolation.cs index a4259ab2..29fffef3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/ApiMsWinNetIsolation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/ApiMsWinNetIsolation.cs @@ -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 appContainerSids) + public static unsafe WIN32_ERROR NetworkIsolationSetAppContainerConfig(ReadOnlySpan appContainerSids) { fixed (SID_AND_ATTRIBUTES* pAppContainerSids = appContainerSids) { - return NetworkIsolationSetAppContainerConfig((uint)appContainerSids.Length, pAppContainerSids); + uint retVal = NetworkIsolationSetAppContainerConfig((uint)appContainerSids.Length, pAppContainerSids); + return *(WIN32_ERROR*)&retVal; } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/FlexibleArray.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/FlexibleArray.cs index e4a4ba5e..97e163ec 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/FlexibleArray.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/FlexibleArray.cs @@ -3,8 +3,8 @@ namespace Snap.Hutao.Win32.Foundation; -internal unsafe struct FlexibleArray - where T : unmanaged +internal unsafe struct FlexibleArray + where TElement : unmanaged { - public T* Reference; + public TElement* Reference; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/PSID.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/PSID.cs index 8978cf35..f039093d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/PSID.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/Foundation/PSID.cs @@ -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; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/Common/SHITEMID.cs b/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/Common/SHITEMID.cs index 9c13d012..0efd13a6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/Common/SHITEMID.cs +++ b/src/Snap.Hutao/Snap.Hutao/Win32/UI/Shell/Common/SHITEMID.cs @@ -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;