Compare commits

..

29 Commits

Author SHA1 Message Date
RMBGAME 1d0b16895f 💄 UI Adaptive 2024-08-30 20:11:38 +08:00
k1mlka 65d73c2e50 🚧 重构加速器页面到单独的页面 2024-08-30 19:26:53 +08:00
k1mlka 83c390e446 🐛 通过脚本开关来确定是否显示脚本配置页面 2024-08-30 19:20:15 +08:00
k1mlka 31f5fa5ea6 🚧 DNS检测项目输入项调整 2024-08-30 17:19:44 +08:00
k1mlka f42c7cbd30 🐛 尝试修复STUN服务器初始不显示问题 2024-08-30 16:04:07 +08:00
k1mlka 3cb592ce7e 🚧 加速服务显示子项树形结构 2024-08-30 15:54:05 +08:00
k1mlka d5fa02e9a4 Merge remote-tracking branch 'origin/develop' into feature/networktestservice 2024-08-30 13:58:53 +08:00
k1mlka cbb2fac6ea 🐛 停止加速时,不显示脚本配置页面 2024-08-30 13:36:05 +08:00
RMBGAME 35747253b8 Feature/ipc console log (#3463)
* 🎨 LogConsole

* 🐛 ref

* 💄 ProxyLog UI

* 🐛 WebApplication Logging

* 🐛 Misc

* 🎨 Log format

* 🎨 LogConsoleService.ConsoleMaxCharCount

* 🐛 Misc

---------

Co-authored-by: Aigio Liu <lysoft.pw@foxmail.com>
2024-08-30 13:33:18 +08:00
k1mlka 17c45fdd65 🚧加速器页面Tab栏目调整,无脚本启动时隐藏 2024-08-30 13:26:23 +08:00
k1mlka 88f02e3faf 🚧 网络测试宽度与项目背景色调整 2024-08-29 18:05:41 +08:00
k1mlka 35a0c4d05f 🐛 google doh 地址修正 2024-08-29 18:04:49 +08:00
k1mlka ca7fa0bc97 Merge remote-tracking branch 'origin/develop' into feature/networktestservice 2024-08-29 14:34:51 +08:00
k1mlka a3811a97a0 🚧 清理未启用的代码3 2024-08-28 18:47:03 +08:00
k1mlka dc6723f2e5 🚧 清理未启用的代码2 2024-08-28 18:40:45 +08:00
k1mlka e14f169cb0 🚧 清理未启用的代码 2024-08-28 18:38:40 +08:00
k1mlka 235a1abf34 🚧 调整输入框宽度 2024-08-28 18:36:01 +08:00
k1mlka 95c5a9e143 🐛 doh服务器地址修正 2024-08-28 15:20:14 +08:00
k1mlka 94f47b0165 ✨ doh检测 2024-08-28 15:14:31 +08:00
k1mlka 08beb69865 🚧 加速器页面及逻辑调整 2024-08-28 11:38:53 +08:00
k1mlka 0505158281 ✨ 完善网络测试页面 2024-08-23 12:41:06 +08:00
k1mlka 1fe7986e08 🚧 业务功能实现 2024-08-22 15:11:25 +08:00
k1mlka a469e29438 🚧 网路检测页面 2024-08-20 17:58:58 +08:00
yingpanwang ec028a4a5c 🚧 调整代码,添加网络环境检查系列方法 2024-08-20 17:18:53 +08:00
yingpanwang b2031998d8 🎨 调整命名 2024-08-16 16:38:27 +08:00
wangyingpan 8affd6a3f5 ⚡️ 添加 DNS Over Https 测试方式, 调整 DNS解析格式 2024-07-17 15:36:59 +08:00
wangyingpan 923f2eaf94 ✨ 添加dns 测试 2024-07-15 15:59:43 +08:00
wangyingpan 3a48c00ec6 🚧 清理日志 2024-07-11 14:09:01 +08:00
wangyingpan e3f40a3e40 ✨ 添加网络测试服务 NetworkTestService 2024-07-11 13:56:57 +08:00
21 changed files with 1479 additions and 146 deletions
@@ -0,0 +1,203 @@
using Avalonia.Reactive;
using Avalonia.Xaml.Interactions.Responsive;
using Avalonia.Xaml.Interactivity;
namespace BD.WTTS.Behaviors;
/// <summary>
/// Observes <see cref="Behavior{T}.AssociatedObject"/> control or <see cref="SourceControl"/> control <see cref="Visual.Bounds"/> property changes and if triggered sets or removes style classes when conditions from <see cref="AdaptiveClassSetter"/> are met.
/// </summary>
public class AdaptiveBehavior : Behavior<Control>
{
private IDisposable? _disposable;
private AvaloniaList<AdaptiveClassSetter>? _setters;
/// <summary>
/// Identifies the <seealso cref="SourceControl"/> avalonia property.
/// </summary>
public static readonly StyledProperty<Control?> SourceControlProperty =
AvaloniaProperty.Register<AdaptiveBehavior, Control?>(nameof(SourceControl));
/// <summary>
/// Identifies the <seealso cref="TargetControl"/> avalonia property.
/// </summary>
public static readonly StyledProperty<Control?> TargetControlProperty =
AvaloniaProperty.Register<AdaptiveBehavior, Control?>(nameof(TargetControl));
/// <summary>
/// Identifies the <seealso cref="Setters"/> avalonia property.
/// </summary>
public static readonly DirectProperty<AdaptiveBehavior, AvaloniaList<AdaptiveClassSetter>> SettersProperty =
AvaloniaProperty.RegisterDirect<AdaptiveBehavior, AvaloniaList<AdaptiveClassSetter>>(nameof(Setters), t => t.Setters);
/// <summary>
/// Gets or sets the the source control that <see cref="Visual.BoundsProperty"/> property are observed from, if not set <see cref="Behavior{T}.AssociatedObject"/> is used. This is a avalonia property.
/// </summary>
[ResolveByName]
public Control? SourceControl
{
get => GetValue(SourceControlProperty);
set => SetValue(SourceControlProperty, value);
}
/// <summary>
/// Gets or sets the target control that class name that should be added or removed when triggered, if not set <see cref="Behavior{T}.AssociatedObject"/> is used or <see cref="AdaptiveClassSetter.TargetControl"/> from <see cref="AdaptiveClassSetter"/>. This is a avalonia property.
/// </summary>
[ResolveByName]
public Control? TargetControl
{
get => GetValue(TargetControlProperty);
set => SetValue(TargetControlProperty, value);
}
/// <summary>
/// Gets adaptive class setters collection. This is a avalonia property.
/// </summary>
[Content]
public AvaloniaList<AdaptiveClassSetter> Setters => _setters ??= new AvaloniaList<AdaptiveClassSetter>();
/// <inheritdoc/>
protected override void OnAttachedToVisualTree()
{
base.OnAttachedToVisualTree();
StopObserving();
StartObserving();
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree()
{
base.OnDetachedFromVisualTree();
StopObserving();
}
private void StartObserving()
{
var sourceControl = GetValue(SourceControlProperty) is not null
? SourceControl
: AssociatedObject;
if (sourceControl is not null)
{
_disposable = ObserveBounds(sourceControl);
}
}
private void StopObserving()
{
_disposable?.Dispose();
}
private IDisposable ObserveBounds(Control sourceControl)
{
if (sourceControl is null)
{
throw new ArgumentNullException(nameof(sourceControl));
}
return sourceControl.GetObservable(Visual.BoundsProperty)
.Subscribe(new AnonymousObserver<Rect>(bounds => ValueChanged(sourceControl, Setters, bounds)));
}
private void ValueChanged(Control? sourceControl, AvaloniaList<AdaptiveClassSetter>? setters, Rect bounds)
{
if (sourceControl is null || setters is null)
{
return;
}
foreach (var setter in setters)
{
var isMinOrMaxWidthSet = setter.IsSet(AdaptiveClassSetter.MinWidthProperty)
|| setter.IsSet(AdaptiveClassSetter.MaxWidthProperty);
var widthConditionTriggered = GetResult(setter.MinWidthOperator, bounds.Width, setter.MinWidth)
&& GetResult(setter.MaxWidthOperator, bounds.Width, setter.MaxWidth);
var isMinOrMaxHeightSet = setter.IsSet(AdaptiveClassSetter.MinHeightProperty)
|| setter.IsSet(AdaptiveClassSetter.MaxHeightProperty);
var heightConditionTriggered = GetResult(setter.MinHeightOperator, bounds.Height, setter.MinHeight)
&& GetResult(setter.MaxHeightOperator, bounds.Height, setter.MaxHeight);
var isAddClassTriggered = isMinOrMaxWidthSet switch
{
true when !isMinOrMaxHeightSet => widthConditionTriggered,
false when isMinOrMaxHeightSet => heightConditionTriggered,
true when isMinOrMaxHeightSet => widthConditionTriggered && heightConditionTriggered,
_ => false
};
var targetControl = setter.GetValue(AdaptiveClassSetter.TargetControlProperty) is not null
? setter.TargetControl
: GetValue(TargetControlProperty) is not null
? TargetControl
: AssociatedObject;
if (targetControl is not null)
{
var className = setter.ClassName;
var isPseudoClass = setter.IsPseudoClass;
if (isAddClassTriggered)
{
Add(targetControl, className, isPseudoClass);
}
else
{
Remove(targetControl, className, isPseudoClass);
}
}
}
}
private bool GetResult(ComparisonConditionType comparisonConditionType, double property, double value)
{
return comparisonConditionType switch
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
ComparisonConditionType.Equal => property == value,
// ReSharper disable once CompareOfFloatsByEqualityOperator
ComparisonConditionType.NotEqual => property != value,
ComparisonConditionType.LessThan => property < value,
ComparisonConditionType.LessThanOrEqual => property <= value,
ComparisonConditionType.GreaterThan => property > value,
ComparisonConditionType.GreaterThanOrEqual => property >= value,
_ => throw new ArgumentOutOfRangeException()
};
}
private static void Add(Control targetControl, string? className, bool isPseudoClass)
{
if (className is null || string.IsNullOrEmpty(className) || targetControl.Classes.Contains(className))
{
return;
}
if (isPseudoClass)
{
((IPseudoClasses)targetControl.Classes).Add(className);
}
else
{
targetControl.Classes.Add(className);
}
}
private static void Remove(Control targetControl, string? className, bool isPseudoClass)
{
if (className is null || string.IsNullOrEmpty(className) || !targetControl.Classes.Contains(className))
{
return;
}
if (isPseudoClass)
{
((IPseudoClasses)targetControl.Classes).Remove(className);
}
else
{
targetControl.Classes.Remove(className);
}
}
}
@@ -11,6 +11,7 @@
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.UI.Views.Windows")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Converters")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Markup")]
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Behaviors")]
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Services")]
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins")]
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins.Abstractions")]
@@ -97,7 +97,7 @@
</Design.PreviewWith>
<Styles.Resources>
<Thickness x:Key="AppPageMargin">20</Thickness>
<Thickness x:Key="AppPageMargin">20,10,20,20</Thickness>
</Styles.Resources>
<Style Selector="spp|PageBase">
@@ -313,7 +313,7 @@
<Setter Property="Grid.Row" Value="2" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Margin" Value="0 12" />
<Setter Property="Margin" Value="0 12 0 0" />
</Style>
<Style Selector="^ /template/ Panel#PageDetails">
@@ -8,18 +8,18 @@ public partial interface IDnsAnalysisService // 公共常量定义
{
#region DNS 常量
const string DNS_Ali_DohAddres = "https://dns.alidns.com";
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/dns-query";
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/dns-query";
const string DNS_Ali_DohAddres = "https://dns.alidns.com/resolve";
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/resolve";
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/resolve";
const string Dnspod_DohAddres = "https://1.12.12.12/dns-query";
const string Dnspod_DohAddres2 = "https://doh.pub/dns-query";
const string Dnspod_DohAddres3 = "https://120.53.53.53/dns-query";
const string Dnspod_DohAddres = "https://1.12.12.12/resolve";
const string Dnspod_DohAddres2 = "https://doh.pub/resolve";
const string Dnspod_DohAddres3 = "https://120.53.53.53/resolve";
const string Google_DohAddres = "https://dns.google";
const string Cloudflare_DohAddres = "https://cloudflare-dns.com";
const string DohAddres_360 = "https://doh.360.cn/dns-query";
const string TUNA_DohAddres = "https://101.6.6.6:8443/dns-query";
const string Google_DohAddres = "https://dns.google/resolve";
const string Cloudflare_DohAddres = "https://cloudflare-dns.com/resolve";
const string DohAddres_360 = "https://doh.360.cn/resolve";
const string TUNA_DohAddres = "https://101.6.6.6:8443/resolve";
const string PrimaryDNS_IPV6_Ali = "2400:3200::1";
@@ -60,5 +60,5 @@ public partial interface IDnsAnalysisService // 公共常量定义
public static readonly IPEndPoint Cloudflare2 = new(IPAddress.Parse("1.0.0.1"), 53);
}
#endregion
#endregion DNS 常量
}
@@ -0,0 +1,22 @@
namespace BD.WTTS.Enums;
/// <summary>
/// 网络环境检查步骤
/// </summary>
public enum NetworkEnvCheckStep
{
/// <summary>
/// Hosts文件检查
/// </summary>
HostsFileCheck = 1,
/// <summary>
/// 网卡检查
/// </summary>
NetworkInterfaceCheck = 2,
/// <summary>
/// LSP检查
/// </summary>
LSPCheck = 3
}
@@ -66,6 +66,9 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
services.AddSingleton(_ => certificateManager.Task.GetAwaiter().GetResult());
#endif
}
services.AddSingleton<INetworkTestService, NetworkTestService>();
//if (startup.HasIPCRoot)
//{
// services.AddSingleton<IAcceleratorService, BackendAcceleratorServiceImpl>();
@@ -0,0 +1,322 @@
using System.Net.NetworkInformation;
namespace BD.WTTS.Services.Implementation;
public class NetworkEnvCheckService
{
const string HOST_FILE_PATH =
#if WINDOWS
@"C:\Windows\System32\drivers\etc\hosts";
#else
"/etc/hosts";
#endif
public async Task CheckNetworkEnvAsync(IProgress<(NetworkEnvCheckStep Step, bool HasError, string Msg)> progress, CancellationToken cancellationToken = default)
{
await Parallel.ForAsync(
(int)NetworkEnvCheckStep.HostsFileCheck,
(int)NetworkEnvCheckStep.LSPCheck + 1,
cancellationToken,
async (step, ct) =>
{
(bool pass, string msg) = step switch
{
(int)NetworkEnvCheckStep.HostsFileCheck => await CheckHostsAsync(),
(int)NetworkEnvCheckStep.NetworkInterfaceCheck => CheckNetworkInterfaces(),
(int)NetworkEnvCheckStep.LSPCheck =>
#if WINDOWS
CheckLSP(HandleWSAProtocolInfo),
#else
(true, string.Empty),
#endif
_ => (true, string.Empty)
};
progress.Report(((NetworkEnvCheckStep)step, pass, msg));
}
).ConfigureAwait(false);
}
private static Task<(bool Pass, string Msg)> CheckHostsAsync()
{
bool hostFileExist = File.Exists(HOST_FILE_PATH);
if (!hostFileExist)
return Task.FromResult((false, "未找到 hosts 文件"));
return Task.FromResult((hostFileExist, string.Empty));
}
private static (bool Pass, string ErrorMsg) CheckNetworkInterfaces(NetworkInterface[]? interfaces = null)
{
interfaces ??= NetworkInterface.GetAllNetworkInterfaces();
if (interfaces == null || interfaces.Length == 0)
return (false, "未获取到网络适配器信息!");
// 如何判断虚拟网卡?
var useableNetworkInterfaces =
interfaces.Where(x =>
!x.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase) &&
(x.NetworkInterfaceType == NetworkInterfaceType.Ethernet || x.NetworkInterfaceType == NetworkInterfaceType.Wireless80211));
if (!useableNetworkInterfaces.Any())
return (false, "未获取到可用的以太网,无线网络适配器");
var checkResults =
useableNetworkInterfaces.Select(x => new
{
Source = x,
Result = CheckNetworkInterface(x)
});
if (checkResults.Any(x => x.Result.Pass))
return (true, string.Empty);
var firstError = checkResults.First(x => !x.Result.Pass);
return (firstError.Result.Pass, $"{firstError.Source.Name}|{firstError.Source.Description} {firstError.Result.ErrorMsg}");
}
private static (bool Pass, string ErrorMsg) CheckNetworkInterface(NetworkInterface @interface)
{
var name = @interface.Name;
var desc = @interface.Description;
var ipProps = @interface.GetIPProperties();
if (@interface.OperationalStatus != OperationalStatus.Up)
return (false, "未启用网卡");
if (ipProps == null)
return (false, $"获取网络信息失败!");
bool hasIPv4 = ipProps.UnicastAddresses
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
bool hasIPv6 = ipProps.UnicastAddresses
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetworkV6);
if (!hasIPv4 && !hasIPv6)
return (false, "网络适配器未启用TCP/IP服务");
// 如何判断虚拟网卡?
var dns = ipProps.DnsAddresses;
#if WINDOWS
var isDHCPEnable = ipProps.GetIPv4Properties().IsDhcpEnabled;
#endif
return (true, string.Empty);
}
private static (bool Pass, string ErrorMsg) CheckLSP(CheckLSPPredicate predicate)
{
int bufferSize = 0;
int error = 0;
// 获取 buffer 的大小
_ = WSCEnumProtocols(null, IntPtr.Zero, ref bufferSize, ref error);
IntPtr protocolBuffer = Marshal.AllocHGlobal(bufferSize);
try
{
int result = WSCEnumProtocols(null, protocolBuffer, ref bufferSize, ref error);
if (result == -1)
{
return (false, "获取LSP信息失败!");
}
int structSize = Marshal.SizeOf(typeof(WSAPROTOCOL_INFO));
int count = bufferSize / structSize;
for (int i = 0; i < count; i++)
{
IntPtr item = new(protocolBuffer + (i * structSize));
WSAPROTOCOL_INFO protocolInfo = Marshal.PtrToStructure<WSAPROTOCOL_INFO>(item);
#if DEBUG
// Console.WriteLine($"Protocol: {protocolInfo.szProtocol}");
// Console.WriteLine($"Address Family: {protocolInfo.iAddressFamily}");
// Console.WriteLine($"Socket Type: {protocolInfo.iSocketType}");
// Console.WriteLine($"Protocol: {protocolInfo.iProtocol}");
// Console.WriteLine($"Provider ID: {protocolInfo.ProviderId}");
// Console.WriteLine();
#endif
var handle = predicate(in protocolInfo);
if (!handle)
return (false, "LSP响应异常!");
}
return (true, string.Empty);
}
finally
{
Marshal.FreeHGlobal(protocolBuffer);
}
}
#region P/Invoke
/// <summary>
/// 处理判断 协议信息是否有效
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
static bool HandleWSAProtocolInfo(in WSAPROTOCOL_INFO info)
{
const int MAX = 255;
StringBuilder pathBuilder = new StringBuilder(MAX);
int pathLength = MAX;
Guid queryProviderId = info.ProviderId;
int result = WSCGetProviderPath(ref queryProviderId, pathBuilder, ref pathLength, out int errorCode);
if (result != 0)
{
// 获取失败
}
// path 可能包含系统环境变量例如 %SystemRoot%
// 是否需要解析
string path = pathBuilder.ToString();
Console.WriteLine("path:" + path);
return true;
}
[DllImport("Ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int WSCEnumProtocols(
int[]? lpiProtocols,
IntPtr lpProtocolBuffer,
ref int lpdwBufferLength,
ref int lpErrno);
[DllImport("Ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int WSCGetProviderPath(
[In] ref Guid providerId,
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder providerDllPath,
[In, Out] ref int providerDllPathLength,
[Out] out int lpErrno
);
private delegate bool CheckLSPPredicate(in WSAPROTOCOL_INFO protocolInfo);
/// <summary>
/// WSAPROTOCOL_INFO 结构用于存储或检索给定协议的完整信息。
/// <see cref="https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/ns-winsock2-wsaprotocol_infoa"/>
/// </summary>
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "<挂起>")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WSAPROTOCOL_INFO
{
/// <summary>
/// 描述协议提供的服务的位掩码
/// </summary>
public uint dwServiceFlags1;
/// <summary>
/// 保留给其他协议属性定义。
/// </summary>
public uint dwServiceFlags2;
/// <summary>
/// 保留给其他协议属性定义。
/// </summary>
public uint dwServiceFlags3;
/// <summary>
/// 保留给其他协议属性定义。
/// </summary>
public uint dwServiceFlags4;
/// <summary>
/// 一组标志,提供有关如何在 Winsock 目录中表示此协议的信息
/// </summary>
public uint dwProviderFlags;
/// <summary>
/// 服务提供商供应商) 分配给提供程序的 GUID
/// </summary>
public Guid ProviderId;
/// <summary>
/// 由 WS2_32.DLL 为每个WSAPROTOCOL_INFO结构分配的唯 一 标识符。
/// </summary>
public uint dwCatalogEntryId;
public WSAPROTOCOLCHAIN ProtocolChain;
/// <summary>
/// 协议版本标识符。
/// </summary>
public int iVersion;
/// <summary>
/// 要作为地址系列参数传递给 套接字 或 WSASocket 函数的
/// </summary>
public int iAddressFamily;
/// <summary>
/// 最大地址长度(以字节为单位)。
/// </summary>
public int iMaxSockAddr;
/// <summary>
/// 最小地址长度(以字节为单位)。
/// </summary>
public int iMinSockAddr;
public int iSocketType;
public int iProtocol;
public int iProtocolMaxOffset;
/// <summary>
/// 目前,这些值是 BIGENDIAN 和 LITTLEENDIAN) (清单常量,分别指示值为 0 和 1 的 big-endian 或 little-endian
/// </summary>
public int iNetworkByteOrder;
public int iSecurityScheme;
/// <summary>
/// 协议支持的最大消息大小(以字节为单位)
/// </summary>
public uint dwMessageSize;
public uint dwProviderReserved;
/// <summary>
/// 一个字符数组,其中包含标识协议的可读名称,例如“MSAFD Tcpip [UDP/IP]”。 允许的最大字符数为 WSAPROTOCOL_LEN,定义为 255。
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szProtocol;
}
/// <summary>
/// WSAPROTOCOLCHAIN 结构包含构成协议链的目录条目标识符的计数列表。
/// <see cref="https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/ns-winsock2-wsaprotocolchain"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct WSAPROTOCOLCHAIN
{
/// <summary>
/// 链的长度(以字节为单位)
/// 将 ChainLen 设置为零表示分层协议
/// 将 ChainLen 设置为 1 指示基本协议
/// 将 ChainLen 设置为大于 1 表示协议链
/// ChainEntries[MAX_PROTOCOL_CHAIN]
/// </summary>
public int ChainLen;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public uint[] ChainEntries;
}
#endregion P/Invoke
}
@@ -50,9 +50,30 @@ internal partial class NetworkTestService : INetworkTestService
#endregion Ping
#region Upload/Download Speed Test
#region Open/Upload/Download Speed Test
public async Task<(bool success, double? rate)> TestUploadSpeedAsync(
public async Task<(bool Success, long? DelayMs)> TestOpenUrlAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default)
{
using var testClient = httpClientFunc?.Invoke() ?? new HttpClient();
Stopwatch watch = Stopwatch.StartNew();
try
{
var resp = await testClient.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken);
watch.Stop();
return (true, watch.ElapsedMilliseconds);
}
catch (Exception ex)
{
Log.Error(nameof(NetworkTestService), ex, "测试打开 URL 异常");
return (false, default);
}
}
public async Task<(bool Success, double? Rate)> TestUploadSpeedAsync(
string uploadServerUrl,
byte[] uploadBytes,
CancellationToken cancellationToken = default
@@ -70,15 +91,15 @@ internal partial class NetworkTestService : INetworkTestService
static MultipartFormDataContent BuildUploadContent(byte[] data)
{
MultipartFormDataContent cotent = new MultipartFormDataContent();
MultipartFormDataContent content = new MultipartFormDataContent();
cotent.Add(new ByteArrayContent(data));
content.Add(new ByteArrayContent(data));
return cotent;
return content;
}
}
public async Task<(bool success, double? rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default)
public async Task<(bool Success, double? Rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default)
{
using HttpClient testClient = new HttpClient();
@@ -94,7 +115,7 @@ internal partial class NetworkTestService : INetworkTestService
/// <param name="uploadBytes"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
static async Task<(bool success, double? rate)> TestUploadSpeedCoreAsync(
static async Task<(bool Success, double? Rate)> TestUploadSpeedCoreAsync(
Func<HttpClient> httpClientFunc,
Func<byte[], HttpContent> uploadContentFunc,
string uploadServerUrl,
@@ -134,7 +155,7 @@ internal partial class NetworkTestService : INetworkTestService
/// <param name="url"></param>
/// <param name="httpClientFunc"></param>
/// <returns></returns>
static async Task<(bool success, double? rate)> TestDownloadSpeedCoreAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default)
static async Task<(bool Success, double? Rate)> TestDownloadSpeedCoreAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default)
{
var httpClient = httpClientFunc?.Invoke() ?? new HttpClient();
@@ -175,7 +196,7 @@ internal partial class NetworkTestService : INetworkTestService
return megabits / seconds; // 返回速率,单位是 Mbps
}
#endregion Upload/Download Speed Test
#endregion Open/Upload/Download Speed Test
#region STUN 测试
@@ -354,13 +375,7 @@ internal partial class NetworkTestService : INetworkTestService
DnsQueryAnswerRecord.DnsRecordType dnsRecordType = DnsQueryAnswerRecord.DnsRecordType.A,
CancellationToken cancellationToken = default)
{
var handler = new HttpClientHandler
{
UseCookies = false,
UseProxy = false,
Proxy = HttpNoProxy.Instance,
};
using var client = new HttpClient(handler);
using HttpClient client = new HttpClient();
string queryUrl = $"{dohServer}?name={testDomain}&type={dnsRecordType}";
@@ -750,7 +765,7 @@ internal partial class NetworkTestService : INetworkTestService
}
}
class DnsQueryAnswerRecordConverter : System.Text.Json.Serialization.JsonConverter<DnsQueryAnswerRecord>
private class DnsQueryAnswerRecordConverter : System.Text.Json.Serialization.JsonConverter<DnsQueryAnswerRecord>
{
public override DnsQueryAnswerRecord? Read(ref Utf8JsonReader reader, Type typeToConvert, SystemTextJsonSerializerOptions options)
{
@@ -6,6 +6,8 @@ namespace BD.WTTS.Services;
internal interface INetworkTestService : IHttpRequestTestService, IStunTestService
{
static INetworkTestService Instance => Ioc.Get<INetworkTestService>();
/// <summary>
/// Ping
/// </summary>
@@ -50,6 +52,15 @@ internal interface INetworkTestService : IHttpRequestTestService, IStunTestServi
internal interface IHttpRequestTestService
{
/// <summary>
/// 测试打开网址
/// </summary>
/// <param name="url"></param>
/// <param name="httpClientFunc"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(bool Success, long? DelayMs)> TestOpenUrlAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default);
/// <summary>
/// 测试上传速度
/// </summary>
@@ -57,7 +68,7 @@ internal interface IHttpRequestTestService
/// <param name="uploadBytes"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(bool success, double? rate)> TestUploadSpeedAsync(
Task<(bool Success, double? Rate)> TestUploadSpeedAsync(
string uploadServerUrl,
byte[] uploadBytes,
CancellationToken cancellationToken = default
@@ -69,7 +80,7 @@ internal interface IHttpRequestTestService
/// <param name="downloadUrl"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(bool success, double? rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default);
Task<(bool Success, double? Rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default);
}
internal interface IStunTestService
@@ -1,6 +1,7 @@
// ReSharper disable once CheckNamespace
using Avalonia.Data;
using Google.Protobuf.WellKnownTypes;
using Org.BouncyCastle.Bcpg.OpenPgp;
using System.Linq;
namespace BD.WTTS.Services;
@@ -151,6 +152,7 @@ public sealed partial class ProxyService
ProxyStarting = false;
ProxyStatus = startOrStop;
IsAnyProxyScripts = ProxyStatus && IsEnableScript;
}
}
@@ -260,6 +262,16 @@ public sealed partial class ProxyService
[Reactive]
public bool ProxyStatus { get; set; }
[Reactive]
public bool IsAnyProxyScripts { get; set; }
private bool CheckProxyScriptsEnable()
{
if (!ProxyScripts.Items.Any_Nullable())
return false;
return ProxyScripts.Items!.Any(w => !w.Disable);
}
#endregion 代理状态启动退出
static bool IsProgramStartupRunProxy()
@@ -1,7 +1,8 @@
using AppResources = BD.WTTS.Client.Resources.Strings;
using BD.WTTS.Client.Resources;
using BD.WTTS.UI.Views.Pages;
using STUN.StunResult;
using STUN.Enums;
namespace BD.WTTS.UI.ViewModels;
@@ -12,6 +13,7 @@ public sealed partial class AcceleratorPageViewModel
readonly IPlatformService platformService = IPlatformService.Instance;
readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance;
readonly ICertificateManager certificateManager = ICertificateManager.Constants.Instance;
readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public AcceleratorPageViewModel()
{
@@ -22,6 +24,29 @@ public sealed partial class AcceleratorPageViewModel
#endif
//ProxyService.Current.ProxyStatus = !ProxyService.Current.ProxyStatus;
await ProxyService.Current.StartOrStopProxyService(!ProxyService.Current.ProxyStatus);
if (ProxyService.Current.ProxyStatus == false)
return;
// Create new ProxyEnableDomain for 加速服务 page
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
.Select(list => new ProxyDomainGroupViewModel
{
Name = list.Name,
IconUrl = list.IconUrl ?? string.Empty,
EnableProxyDomainVMs = new(
list.Items!
.Where(i => i.ThreeStateEnable == true)
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
i.Items?
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
.ToList()))
.ToList()),
})
.ToList();
EnableProxyDomainGroupVMs = enableGroupDomain.AsReadOnly();
});
RefreshCommand = ReactiveCommand.Create(async () =>
@@ -29,6 +54,7 @@ public sealed partial class AcceleratorPageViewModel
if (_initializeTime > DateTime.Now.AddSeconds(-2))
{
Toast.Show(ToastIcon.Warning, Strings.Warning_DoNotOperateFrequently);
return;
}
@@ -43,10 +69,65 @@ public sealed partial class AcceleratorPageViewModel
GameAcceleratorService.Current.LoadGames();
});
SelectedSTUNAddress = STUNAddress[0];
NATCheckCommand = ReactiveCommand.CreateFromTask(async () =>
{
var natCheckResult = await networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown };
var (netSucc, _) = await networkTestService.TestOpenUrlAsync("https://www.baidu.com");
var natStatus = natCheckResult.NatType switch
{
NatType.OpenInternet or NatType.FullCone => NatTypeSimple.Open,
NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => NatTypeSimple.Moderate,
NatType.Symmetric or NatType.UdpBlocked => NatTypeSimple.Strict,
NatType.Unknown or NatType.UnsupportedServer or _ => NatTypeSimple.Unknown,
};
PublicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown";
LocalEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown";
return (natStatus, netSucc);
});
NATCheckCommand
.IsExecuting
.ToPropertyEx(this, x => x.IsNATChecking);
DNSCheckCommand = ReactiveCommand.CreateFromTask(async () =>
{
var testDomain = DomainPendingTest == string.Empty ? "steamcommunity.com" : DomainPendingTest;
try
{
long delayMs;
IPAddress[] address;
if (ProxySettings.UseDoh)
{
var configDoh = ProxySettings.CustomDohAddres.Value ?? string.Empty;
(delayMs, address) = await networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
}
else
{
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
(delayMs, address) = await networkTestService.TestDNSAsync(testDomain, configDns, 53);
}
DNSTestDelay = delayMs + "ms ";
DNSTestResult = "" + address.FirstOrDefault() ?? "0.0.0.0";
}
catch (Exception ex)
{
Log.Error(nameof(AcceleratorPageViewModel), ex.ToString());
DNSTestDelay = string.Empty;
DNSTestResult = "error";
}
});
DNSCheckCommand
.IsExecuting
.ToPropertyEx(this, x => x.IsDNSChecking);
ProxySettingsCommand = ReactiveCommand.Create(() =>
{
var vm = new ProxySettingsWindowViewModel();
IWindowManager.Instance.ShowTaskDialogAsync(vm, vm.Title, pageContent: new ProxySettingsPage(), isOkButton: false);
_ = IWindowManager.Instance.ShowTaskDialogAsync(vm, vm.Title, pageContent: new ProxySettingsPage(), isOkButton: false);
});
if (IApplication.IsDesktop())
@@ -73,6 +154,7 @@ public sealed partial class AcceleratorPageViewModel
}
#if LINUX
public bool EnvironmentCheck()
{
try
@@ -93,6 +175,7 @@ public sealed partial class AcceleratorPageViewModel
return false;
}
}
#endif
public void TrustCer_OnClick()
@@ -1,12 +1,145 @@
using BD.WTTS.Client.Resources;
using Avalonia.Controls;
using Avalonia.Media;
using System.Reactive;
namespace BD.WTTS.UI.ViewModels;
public class ProxyDomainGroupViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; set; } = string.Empty;
public string IconUrl { get; set; } = string.Empty;
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
public ProxyDomainGroupViewModel()
{
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
{
var tasks = EnableProxyDomainVMs!.Select(async enableDomain =>
{
enableDomain.DelayMillseconds = "- ms";
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
enableDomain.DelayMillseconds = success switch
{
true when delayMs > 20000 => "Timeout",
true => delayMs.ToString() + " ms",
false => "error",
};
});
await Task.WhenAll(tasks);
});
}
}
public class ProxyDomainViewModel : ReactiveObject
{
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
public string Name { get; }
public ProxyType ProxyType { get; }
public string Url { get; }
[Reactive]
public string DelayMillseconds { get; set; } = string.Empty;
[ObservableAsProperty]
public bool RetestButtonVisible { get; }
[ObservableAsProperty]
public IBrush DelayColor { get; } = null!;
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
{
Name = name;
ProxyType = proxyType;
Url = url;
Children = children?.AsReadOnly();
this.WhenAnyValue(x => x.DelayMillseconds)
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d != string.Empty && d != "-")
.ToPropertyEx(this, x => x.RetestButtonVisible);
const int DelayMiddle = 1000;
this.WhenAnyValue(x => x.DelayMillseconds)
.Select(d => d switch
{
"Timeout" or "error" => Brushes.Red,
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
=> ms switch
{
<= DelayMiddle => Brushes.Green,
> DelayMiddle => Brushes.Orange,
},
_ => (IBrush?)App.Instance.FindResource("TextFillColorPrimaryBrush"),
})
.ToPropertyEx(this, x => x.DelayColor);
}
}
public sealed partial class AcceleratorPageViewModel : TabItemViewModel
{
public enum NatTypeSimple
{
Unknown,
Open,
Moderate,
Strict,
}
public override string Name => Strings.Welcome;
[Reactive]
public string SelectedSTUNAddress { get; set; }
public List<string> STUNAddress { get; } =
[
"stun.syncthing.net",
"stun.hot-chilli.net",
"stun.fitauto.ru",
"stun.miwifi.com",
];
[ObservableAsProperty]
public bool IsNATChecking { get; }
[ObservableAsProperty]
public bool IsDNSChecking { get; }
[Reactive]
public string DomainPendingTest { get; set; } = string.Empty;
[Reactive]
public ReadOnlyCollection<ProxyDomainGroupViewModel>? EnableProxyDomainGroupVMs { get; set; }
[Reactive]
public string LocalEndPoint { get; set; } = string.Empty;
[Reactive]
public string PublicEndPoint { get; set; } = string.Empty;
[Reactive]
public string DNSTestDelay { get; set; } = string.Empty;
[Reactive]
public string DNSTestResult { get; set; } = string.Empty;
public ReactiveCommand<Unit, (NatTypeSimple Nat, bool PingSuccess)> NATCheckCommand { get; }
public ReactiveCommand<Unit, Unit> DNSCheckCommand { get; }
public ICommand StartProxyCommand { get; }
public ICommand RefreshCommand { get; }
@@ -32,4 +165,4 @@ public sealed partial class AcceleratorPageViewModel : TabItemViewModel
public ICommand? TrustCerCommand { get; }
public ICommand? OpenLogFileCommand { get; }
}
}
@@ -14,7 +14,7 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
//public IEnumerable<ExternalProxyType> ProxyTypes { get; }
// = Enum2.GetAll<ExternalProxyType>();
public IEnumerable<string> ProxyDNSs { get; } = new[]
public static IEnumerable<string> ProxyDNSs { get; } = new[]
{
"System Default",
PrimaryDNS_114,
@@ -31,7 +31,7 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
"127.0.0.1",
};
public IEnumerable<string> DohAddress { get; }
public static IEnumerable<string> DohAddress { get; }
= new[] {
Dnspod_DohAddres,
Dnspod_DohAddres2,
@@ -44,4 +44,4 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
DohAddres_360,
TUNA_DohAddres,
};
}
}
@@ -0,0 +1,98 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Pages.AcceleratorServiceStatus"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:enum="using:BD.WTTS.Enums"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:spp="https://steampp.net/ui"
xmlns:ui="using:FluentAvalonia.UI.Controls"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="spp:AcceleratorPageViewModel"
mc:Ignorable="d">
<ScrollViewer
Padding="0,0,10,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding EnableProxyDomainGroupVMs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Spacing="{StaticResource SettingsSpacing}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
<ui:SettingsExpander.Header>
<StackPanel Orientation="Horizontal" Spacing="10">
<spp:Image2
Width="30"
Height="30"
VerticalAlignment="Center"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding IconUrl}"
Stretch="UniformToFill" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
</StackPanel>
</ui:SettingsExpander.Header>
<ui:SettingsExpander.Footer>
<Button
HorizontalAlignment="Right"
Click="ConnectTestButtonOnClick"
Command="{Binding ConnectTestCommand}"
Content="连通性测试"
DockPanel.Dock="Right" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Theme="{StaticResource SettingsExpanderItem2}">
<TreeView ItemsSource="{Binding EnableProxyDomainVMs}" SelectionMode="Single">
<TreeView.ItemContainerTheme>
<ControlTheme BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</ControlTheme>
</TreeView.ItemContainerTheme>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<DockPanel Margin="8,0">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
<Border
Padding="5,0"
CornerRadius="5"
IsVisible="{Binding ProxyType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:ProxyType.ServerAccelerate}}"
ToolTip.Tip="Beta 测试加速项,不稳定且随时可能调整">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#43cbff" />
<GradientStop Offset="1.0" Color="#9708cc" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Text="Beta" />
</Border>
</StackPanel>
<TextBlock
Margin="0,0,4,0"
HorizontalAlignment="Right"
DockPanel.Dock="Right"
Foreground="{Binding DelayColor}"
Text="{Binding DelayMillseconds}"
ToolTip.Tip="此数值代表测试网页打开访问所需时间,仅供参考" />
</DockPanel>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,25 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
namespace BD.WTTS.UI.Views.Pages;
public partial class AcceleratorServiceStatus : UserControl
{
public AcceleratorServiceStatus()
{
InitializeComponent();
}
private void ConnectTestButtonOnClick(object? sender, RoutedEventArgs e)
{
if (sender is Button button)
{
var expander = button.FindAncestorOfType<SettingsExpander>();
if (expander != null) expander.IsExpanded = true;
}
}
}
@@ -0,0 +1,307 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Pages.NetworkCheck"
xmlns="https://github.com/avaloniaui"
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:ms="https://steampp.net/settings"
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:sys="using:System"
xmlns:ui="using:FluentAvalonia.UI.Controls"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="spp:AcceleratorPageViewModel"
mc:Ignorable="d">
<ScrollViewer
Padding="0,0,10,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="8">
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
<ui:SettingsExpander.Header>
<StackPanel Orientation="Horizontal">
<ui:FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="26"
Glyph="&#xE969;" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
Text="NAT 类型检测" />
</StackPanel>
</ui:SettingsExpander.Header>
<ui:SettingsExpander.Footer>
<WrapPanel>
<ui:FAComboBox
x:Name="STUNServer"
Margin="4"
x:FieldModifier="public"
IsEditable="False"
IsEnabled="{Binding !IsNATChecking}"
ItemsSource="{Binding STUNAddress}"
SelectedItem="{Binding SelectedSTUNAddress}"
ToolTip.Tip="STUN 服务器地址">
<ui:FAComboBox.ItemTemplate>
<DataTemplate DataType="sys:String">
<TextBlock Text="{Binding}" />
</DataTemplate>
</ui:FAComboBox.ItemTemplate>
</ui:FAComboBox>
<Button
x:Name="NATCheckButton"
Width="100"
Margin="4"
x:FieldModifier="public"
Content="检测"
IsEnabled="True" />
</WrapPanel>
</ui:SettingsExpander.Footer>
<StackPanel
Margin="24,0,0,0"
Background="Transparent"
Spacing="8">
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock VerticalAlignment="Center" Text="NAT 类型:" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<DockPanel IsVisible="{Binding !IsNATChecking}">
<SelectableTextBlock
x:Name="NATTextBlock"
VerticalAlignment="Center"
Foreground="Gray"
Text="未知" />
<ui:FontIcon
Margin="3,0"
FontSize="20"
Glyph="&#xE946;">
<ToolTip.Tip>
<TextBlock x:Name="NATTypeTip" Text="未知类型" />
</ToolTip.Tip>
</ui:FontIcon>
</DockPanel>
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock VerticalAlignment="Center" Text="本地IP地址:" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<SelectableTextBlock
x:Name="LocalIPAddress"
Foreground="Gray"
IsVisible="{Binding !IsNATChecking}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock
VerticalAlignment="Center"
Text="对外IP地址:"
ToolTip.Tip="若与您路由器 WAN IP 地址相同,则为公共网络" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<SelectableTextBlock
Foreground="Gray"
IsVisible="{Binding !IsNATChecking}"
Text="{Binding PublicEndPoint}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<!--<Border Classes="paper" Margin="-64 0" />-->
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock VerticalAlignment="Center" Text="互联网连通情况:" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
HorizontalAlignment="Right"
IsVisible="{Binding IsNATChecking}" />
<ui:FontIcon
x:Name="PingOK"
HorizontalAlignment="Right"
x:FieldModifier="public"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="26"
Foreground="Green"
Glyph="&#xe73d;" />
<Grid x:Name="PingError" x:FieldModifier="public">
<ui:FontIcon
Margin="3"
HorizontalAlignment="Right"
FontFamily="Segoe Fluent Icons"
FontSize="20"
Foreground="Red"
Glyph="&#xf127;" />
<ui:FontIcon
HorizontalAlignment="Right"
FontSize="26"
Foreground="White"
Glyph="&#xea39;" />
</Grid>
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<!--<Separator Classes="Separator" Margin="-64 0" />-->
<Separator Margin="-64,0" Classes="Horizontal" />
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
VerticalAlignment="Center"
FontWeight="Bold"
Text="相关链接" />
<HyperlinkButton
VerticalAlignment="Center"
Content="解决NAT错误"
NavigateUri="https://support.xbox.com/zh-CN/help/hardware-network/connect-network/xbox-one-nat-error" />
</StackPanel>
</ui:SettingsExpanderItem>
</StackPanel>
</ui:SettingsExpander>
<!-- DNS -->
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
<ui:SettingsExpander.Header>
<StackPanel Orientation="Horizontal">
<ui:FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="26"
Glyph="&#xEDA3;" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
Text="DNS 检测" />
</StackPanel>
</ui:SettingsExpander.Header>
<ui:SettingsExpander.Footer>
<Button
x:Name="DNSCheckButton"
Width="100"
Content="检测"
IsEnabled="True" />
</ui:SettingsExpander.Footer>
<StackPanel Margin="24,0,0,0">
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
<TextBlock
VerticalAlignment="Center"
FontWeight="Bold"
Text="检测项目" />
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="待检测的域名" />
<ui:SettingsExpanderItem.Footer>
<TextBox
x:Name="DomainTextBox"
Width="200"
Watermark="steamcommunity.com" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="{Binding Path=Res.Settings_Proxy_CustomDNS, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox
x:Name="dummy"
Width="200"
IsEditable="False"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.ProxyDNSs}}"
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="{Binding Path=Res.CommunityFix_CustomDohAddres, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox
Width="200"
IsEditable="False"
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.DohAddress}}"
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres}, Mode=OneWay}"
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
<TextBlock
VerticalAlignment="Center"
FontWeight="Bold"
Text="测试结果" />
</ui:SettingsExpanderItem>
<Separator Margin="-64,0" Classes="Separator" />
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="延迟" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsDNSChecking}" />
<SelectableTextBlock Text="{Binding DNSTestDelay}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Background="Transparent">
<TextBlock Text="解析地址" />
<ui:SettingsExpanderItem.Footer>
<Grid>
<ui:ProgressRing
Width="22"
Margin="0,-4,0,-10"
IsVisible="{Binding IsDNSChecking}" />
<SelectableTextBlock Text="{Binding DNSTestResult}" />
</Grid>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</StackPanel>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Org.BouncyCastle.Asn1.Utilities;
namespace BD.WTTS.UI.Views.Pages;
public partial class NetworkCheck : UserControl
{
public NetworkCheck()
{
InitializeComponent();
this.dummy.SelectedItem = ProxySettings.ProxyMasterDns.Value;
this.dummy.Text = ProxySettings.ProxyMasterDns.Value;
}
}
@@ -0,0 +1,47 @@
<UserControl
x:Class="BD.WTTS.UI.Views.Pages.ProxyServiceStatus"
xmlns="https://github.com/avaloniaui"
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:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="spp:AcceleratorPageViewModel"
mc:Ignorable="d">
<ScrollViewer
Padding="0,0,10,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ItemsControl Grid.Column="1" ItemsSource="{Binding EnableProxyScripts^, Source={x:Static s:ProxyService.Current}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type spp:ScriptDTO}">
<spp:AppItem
Title="{Binding Name}"
MaxWidth="135"
Margin="4"
Classes="Vertical"
Description="{Binding AuthorName}"
ImageMargin="20"
ImageStretch="None"
ToolTip.Tip="{Binding Describe}">
<spp:AppItem.Image>
<spp:Image2
FallbackSource="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/script.ico"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding IconUrl}"
Stretch="Uniform" />
</spp:AppItem.Image>
</spp:AppItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BD.WTTS.UI.Views.Pages;
public partial class ProxyServiceStatus : UserControl
{
public ProxyServiceStatus()
{
InitializeComponent();
}
}
@@ -6,13 +6,15 @@
xmlns:enum="using:BD.WTTS.Enums"
xmlns:i="using:Avalonia.Xaml.Interactivity"
xmlns:ia="using:Avalonia.Xaml.Interactions.Core"
xmlns:ir="using:Avalonia.Xaml.Interactions.Responsive"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ms="https://steampp.net/settings"
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:sys="using:System"
xmlns:ui="using:FluentAvalonia.UI.Controls"
Title="{Binding Name, Source={x:Static s:Plugin.Instance}, Mode=OneWay}"
d:DesignHeight="450"
d:DesignHeight="650"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="spp:AcceleratorPageViewModel"
@@ -391,7 +393,6 @@
<Button Content="打开文件夹" />
<Button Content="选择路径" />
</StackPanel>
</ui:SettingsExpander>-->
<!--<ItemsControl>
@@ -421,12 +422,45 @@
</StackPanel>
</StackPanel>
</spp:PageBase.PaneContent>
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,*">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,*">
<Grid.Resources>
<sys:Double x:Key="AdaptiveWidth">800</sys:Double>
</Grid.Resources>
<Grid.Styles>
<Style Selector="StackPanel.small">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style Selector="StackPanel.wide">
<Setter Property="Grid.Row" Value="0" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style Selector="TabStrip.small">
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style Selector="TabStrip.wide">
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</Grid.Styles>
<TabStrip
x:Name="AcceleratorTabs"
VerticalAlignment="Top"
ItemContainerTheme="{StaticResource TabItemStyle}"
Theme="{StaticResource TabStripStyle}">
<i:Interaction.Behaviors>
<spp:AdaptiveBehavior SourceControl="{Binding $parent[TopLevel]}">
<ir:AdaptiveClassSetter
MinWidth="0"
MaxWidth="{StaticResource AdaptiveWidth}"
ClassName="small" />
<ir:AdaptiveClassSetter
MinWidth="{StaticResource AdaptiveWidth}"
MaxWidth="Infinity"
ClassName="wide" />
</spp:AdaptiveBehavior>
</i:Interaction.Behaviors>
<TabStripItem Content="平台加速" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=!ProxyStatus, Mode=OneWay}">
<!--<WrapPanel>-->
<TextBlock Text="平台加速" />
@@ -435,13 +469,13 @@
Padding="5,1"
VerticalAlignment="Center"
CornerRadius="2">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#48C6EF" />
<GradientStop Offset="1.0" Color="#6F86D6" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#48C6EF" />
<GradientStop Offset="1.0" Color="#6F86D6" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Foreground="White"
@@ -449,9 +483,10 @@
</Border>
</WrapPanel>-->
</TabStripItem>
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
<TabStripItem Content="加速脚本" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=IsAnyProxyScripts, Mode=OneWay}" />
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_ProxyLog}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_FlowStatistics}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
<TabStripItem Name="GameAccTab">
<!--<WrapPanel>-->
<TextBlock Text="游戏加速" />
@@ -460,13 +495,13 @@
Padding="5,1"
VerticalAlignment="Center"
CornerRadius="2">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#EEAD92" />
<GradientStop Offset="1.0" Color="#6018DC" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#EEAD92" />
<GradientStop Offset="1.0" Color="#6018DC" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Foreground="White"
@@ -474,9 +509,11 @@
</Border>-->
<!--</WrapPanel>-->
</TabStripItem>
<TabStripItem Content="网络检测" IsVisible="True" />
<TabStripItem Content="dummy_tab_for_transisit_animation" IsVisible="False" />
</TabStrip>
<Carousel
Grid.Row="1"
Grid.Row="2"
Margin="0,10,0,0"
Classes="AnimeSlide"
SelectedIndex="{Binding #AcceleratorTabs.SelectedIndex}">
@@ -558,87 +595,19 @@
</ItemsControl>
</spp:ContentLoader>
</ScrollViewer>
<!-- 加速服务 -->
<spp:AcceleratorServiceStatus />
<!-- 加速脚本 -->
<spp:ProxyServiceStatus />
<!-- 加速日志 -->
<spp:ProxyLog Margin="5,0" />
<!-- 流量统计 -->
<spp:ProxyChartView Margin="5,0" />
<!-- 加速服务 -->
<Grid Margin="0,0,10,0" ColumnDefinitions="*,20,*">
<Border
Grid.Column="0"
Padding="10"
Classes="paper">
<DockPanel>
<TextBlock
Margin="0,0,0,10"
DockPanel.Dock="Top"
FontSize="16"
Text="{x:Static spp:Strings.CommunityFix_AccelerationsEnable}" />
<ScrollViewer>
<ItemsControl ItemsSource="{Binding EnableProxyDomains, Source={x:Static s:ProxyService.Current}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type spp:AccelerateProjectDTO}">
<StackPanel Margin="10">
<DockPanel>
<TextBlock Text="{Binding Name}" />
<Border
Padding="5,0"
HorizontalAlignment="Left"
CornerRadius="5"
IsVisible="{Binding ProxyType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:ProxyType.ServerAccelerate}}"
ToolTip.Tip="Beta 测试加速项,不稳定且随时可能调整">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0.0" Color="#43cbff" />
<GradientStop Offset="1.0" Color="#9708cc" />
</LinearGradientBrush>
</Border.Background>
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Text="Beta" />
</Border>
</DockPanel>
<Separator Classes="Separator" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Border>
<Border
Grid.Column="2"
Padding="10"
Classes="paper">
<DockPanel>
<TextBlock
Margin="0,0,0,10"
DockPanel.Dock="Top"
FontSize="16"
Text="{x:Static spp:Strings.CommunityFix_ScriptsEnable}" />
<ScrollViewer>
<ItemsControl ItemsSource="{Binding EnableProxyScripts^, Source={x:Static s:ProxyService.Current}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type spp:ScriptDTO}">
<StackPanel Margin="10">
<DockPanel>
<TextBlock Text="{Binding Name}" />
</DockPanel>
<Separator Classes="Separator" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Border>
</Grid>
<!-- 游戏加速 -->
<Panel x:DataType="spp:GameAcceleratorPageViewModel">
<Panel.DataContext>
@@ -699,20 +668,30 @@
IsVisible="{Binding Source={x:Static s:GameAcceleratorService.Current}, Path=CurrentAcceleratorGame.IsAccelerated}" />
</Panel>
<!--<TabItem Header="加速日志">
<!-- 网络检测 -->
<spp:NetworkCheck x:Name="NetworkCheckControl" />
</TabItem>-->
<!--<TabItem Header="AD">
</TabItem>-->
</Carousel>
<!-- Side tab of paid game spped up -->
<StackPanel
Grid.ColumnSpan="2"
HorizontalAlignment="Right"
IsVisible="{Binding #GameAccTab.IsSelected}"
Orientation="Horizontal"
Spacing="5">
<i:Interaction.Behaviors>
<spp:AdaptiveBehavior SourceControl="{Binding $parent[TopLevel]}">
<ir:AdaptiveClassSetter
MinWidth="0"
MaxWidth="{StaticResource AdaptiveWidth}"
ClassName="small" />
<ir:AdaptiveClassSetter
MinWidth="{StaticResource AdaptiveWidth}"
MaxWidth="Infinity"
ClassName="wide" />
</spp:AdaptiveBehavior>
</i:Interaction.Behaviors>
<Border
Padding="5,1"
VerticalAlignment="Center"
@@ -784,14 +763,26 @@
</AutoCompleteBox>
</StackPanel>
<!-- Side tab after speed up -->
<StackPanel
Grid.ColumnSpan="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}"
Orientation="Horizontal"
Spacing="8">
<i:Interaction.Behaviors>
<spp:AdaptiveBehavior SourceControl="{Binding $parent[TopLevel]}">
<ir:AdaptiveClassSetter
MinWidth="0"
MaxWidth="{StaticResource AdaptiveWidth}"
ClassName="small" />
<ir:AdaptiveClassSetter
MinWidth="{StaticResource AdaptiveWidth}"
MaxWidth="Infinity"
ClassName="wide" />
</spp:AdaptiveBehavior>
</i:Interaction.Behaviors>
<StackPanel.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<CompiledBinding
@@ -829,7 +820,7 @@
</StackPanel>
<spp:AdControl
Grid.Row="1"
Grid.Row="2"
Grid.Column="1"
Width="165"
Height="390"
@@ -837,4 +828,4 @@
VerticalAlignment="Top"
Standard="Vertical" />
</Grid>
</spp:PageBase>
</spp:PageBase>
@@ -1,8 +1,8 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using static System.Net.Mime.MediaTypeNames;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
namespace BD.WTTS.UI.Views.Pages;
@@ -73,6 +73,36 @@ public partial class AcceleratorPage2 : PageBase<AcceleratorPageViewModel>
GameAccTab.IsVisible = false;
AcceleratorTabs.SelectedIndex = 0;
}
this.Bind(ViewModel, vm => vm.SelectedSTUNAddress, v => v.NetworkCheckControl.STUNServer.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NATCheckCommand, v => v.NetworkCheckControl.NATCheckButton).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.LocalEndPoint, v => v.NetworkCheckControl.LocalIPAddress.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.DNSCheckCommand, v => v.NetworkCheckControl.DNSCheckButton).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DomainPendingTest, v => v.NetworkCheckControl.DomainTextBox.Text).DisposeWith(disposables);
NetworkCheckControl.PingOK.IsVisible = false;
NetworkCheckControl.PingError.IsVisible = false;
NetworkCheckControl.NATCheckButton.Click += (_, _) => NetworkCheckControl.PingError.IsVisible = NetworkCheckControl.PingOK.IsVisible = false;
ViewModel!.NATCheckCommand
.Subscribe(result =>
{
(NetworkCheckControl.NATTextBlock.Text, NetworkCheckControl.NATTypeTip.Text) = result.Nat switch
{
AcceleratorPageViewModel.NatTypeSimple.Open => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"),
AcceleratorPageViewModel.NatTypeSimple.Moderate => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"),
AcceleratorPageViewModel.NatTypeSimple.Strict => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"),
_ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"),
};
if (result.PingSuccess)
{
NetworkCheckControl.PingOK.IsVisible = true;
}
else
{
NetworkCheckControl.PingError.IsVisible = true;
}
}).DisposeWith(disposables);
});
SearchGameBox.DropDownClosed += SearchGameBox_DropDownClosed;
@@ -150,4 +180,4 @@ public partial class AcceleratorPage2 : PageBase<AcceleratorPageViewModel>
// // Log.Error(nameof(AcceleratorPage), ex, "ForceSave fail.");
// //}
//}
}
}