Compare commits

..
Author SHA1 Message Date
Aigio Liu 96a14d69bd 🚧 Misc 2024-08-06 17:09:13 +08:00
Aigio Liu c79fba98d3 🚧 Misc 2024-07-04 17:51:15 +08:00
Aigio Liu 4160bb43c5 🚧 Misc 2024-06-29 00:57:23 +08:00
281 changed files with 2334 additions and 6301 deletions
+5 -5
View File
@@ -68,19 +68,19 @@ jobs:
run: dotnet --info
- name: Build BD.WTTS.Client.AppHost
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
run: dotnet build -c Release src\BD.WTTS.Client.AppHost\BD.WTTS.Client.AppHost.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Build BD.WTTS.Client.Plugins.Accelerator.ReverseProxy
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
run: dotnet build -c Release src\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy\BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Build BD.WTTS.Client.Avalonia.App
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.App\BD.WTTS.Client.Avalonia.App.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Build BD.WTTS.Client.Avalonia.Designer.HostApp
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
run: dotnet build -c Release src\BD.WTTS.Client.Avalonia.Designer.HostApp\BD.WTTS.Client.Avalonia.Designer.HostApp.csproj --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
- name: Test
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q --property:WarningLevel=1 --property:DebugType=pdbonly
run: dotnet test src/BD.WTTS.UnitTest/BD.WTTS.UnitTest.csproj -c Release -p:GeneratePackageOnBuild=false --nologo -v q --property:WarningLevel=1;DebugType=pdbonly
# release:
# name: Release
BIN
View File
Binary file not shown.
-11
View File
@@ -1,11 +0,0 @@
static void WinDivert()
{
var dllWinDivert = Path.Combine(ProjectUtils.ProjPath, "ref", "WinDivert.dll");
var sysWinDivert64 = Path.Combine(ProjectUtils.ProjPath, "ref", "WinDivert64.sys");
byte[][] data = [File.ReadAllBytes(dllWinDivert), File.ReadAllBytes(sysWinDivert64)];
var bytes = MemoryPackSerializer.Serialize(data);
bytes.AsSpan().Reverse();
File.WriteAllBytes(Path.Combine(ProjectUtils.ProjPath, "ref", "WinDivert.mpo"), bytes);
}
+27 -3
View File
@@ -3,6 +3,14 @@ namespace BD.WTTS;
/// <summary>
/// 提供用来获取应用程序信息(如版本号、说明、加载的程序集等)的属性。
/// </summary>
[Mobius3(
"""
Mobius.AssemblyInfo
AssemblyInfo.cs
AssemblyInfo.Version.cs
AssemblyInfo.Version.NativeHost.cs
AssemblyInfo.Version.Setup.Windows.cs
""")]
public static partial class AssemblyInfo
{
const string Version2 = "3.0";
@@ -16,14 +24,14 @@ public static partial class AssemblyInfo
/// <summary>
/// 预览版本号,范围 1~9,只增不减
/// </summary>
const string ver_for_preview = "9";
const string ver_for_preview = "8";
/// <summary>
/// RC 版本号,范围 0~9,只增不减
/// </summary>
const string ver_for_rc = "9";
const string ver_for_rc = "8";
public const string FileVersion = $"{Version2}.{ver_for_rc}1{ver_for_preview}.0";
public const string FileVersion = $"{Version2}.{ver_for_rc}0{ver_for_preview}.0";
//public const string InformationalVersion = Version;
//public const string InformationalVersion = $"{Version}-preview.{ver_for_preview}";
@@ -146,4 +154,20 @@ false
#endif
}
[AttributeUsage(AttributeTargets.All, Inherited = false)]
sealed class Mobius3Attribute : Attribute
{
public Mobius3Attribute()
{
}
public Mobius3Attribute(string str)
{
}
public bool Obsolete { get; set; }
}
+1
View File
@@ -1,3 +1,4 @@
// Mobius Properties\XmlnsDefinition.cs
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
// C# 10 定义全局 using
File diff suppressed because it is too large Load Diff
@@ -11,7 +11,7 @@
<Identity
Name="4651ED44255E.47979655102CE"
Publisher="CN=A90E406B-B2D3-4A23-B061-0FA1D65C4F66"
Version="3.0.207.0" />
Version="3.0.999.0" />
<Properties>
<DisplayName>Watt Toolkit</DisplayName>
@@ -66,15 +66,6 @@
</Application>
</Applications>
<Extensions>
<desktop2:Extension Category="windows.firewallRules">
<desktop2:FirewallRules Executable="Steam++\modules\Accelerator\Steam++.Accelerator.exe">
<desktop2:Rule Direction="in" IPProtocol="TCP" Profile="all"/>
<desktop2:Rule Direction="in" IPProtocol="UDP" Profile="all"/>
</desktop2:FirewallRules>
</desktop2:Extension>
</Extensions>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
@@ -13,7 +13,6 @@ public static class WindowTransparencyLevelExtensions
WindowBackgroundMaterial.Blur => WindowTransparencyLevel.Blur,
WindowBackgroundMaterial.AcrylicBlur => WindowTransparencyLevel.AcrylicBlur,
WindowBackgroundMaterial.Mica => WindowTransparencyLevel.Mica,
WindowBackgroundMaterial.Transparent => WindowTransparencyLevel.Transparent,
_ => WindowTransparencyLevel.None,
};
}
@@ -95,7 +95,7 @@ public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
Func<bool>? cancelCloseAction = null,
bool disableScroll = false)
where TPageViewModel : ViewModelBase
{
{
var td = new TaskDialogEx
{
Title = title,
@@ -111,12 +111,6 @@ public sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
XamlRoot = GetWindowTopLevel(),
};
if (td.XamlRoot == null)
{
Toast.LogAndShowT(new Exception("在 AppWindow 为 Null 时,无法弹出 Taskdialog."));
return false;
}
if (viewModel != null)
{
if (viewModel is IWindowViewModel window)
@@ -7,12 +7,28 @@ namespace BD.WTTS.UI;
partial class App
{
[Mobius(
"""
App.DefaultActualTheme
""")]
const AppTheme _DefaultActualTheme = AppTheme.FollowingSystem;
[Mobius(
"""
App.DefaultActualTheme
""")]
AppTheme IApplication.DefaultActualTheme => _DefaultActualTheme;
[Mobius(
"""
App.mTheme
""")]
AppTheme mTheme = _DefaultActualTheme;
[Mobius(
"""
App.Theme
""")]
public AppTheme Theme
{
get => mTheme;
@@ -50,6 +66,10 @@ partial class App
}
}
[Mobius(
"""
App.SetThemeNotChangeValue
""")]
public void SetThemeNotChangeValue(AppTheme value)
{
var mode = value switch
@@ -71,6 +91,10 @@ partial class App
}
}
[Mobius(
"""
App.SetThemeAccent
""")]
public static void SetThemeAccent(string? colorHex)
{
if (colorHex == null)
+29 -41
View File
@@ -9,14 +9,34 @@ namespace BD.WTTS.UI;
partial class App
{
[Mobius(
"""
App.TrayMenus
""")]
public Dictionary<string, TrayMenuItem> TrayMenus { get; } = new Dictionary<string, TrayMenuItem>();
[Mobius(
"""
App.TrayIcons
""")]
public TrayIcons TrayIcons { get; init; } = new TrayIcons();
[Mobius(
"""
App.trayIconMenus
""")]
readonly Dictionary<string, ICommand> trayIconMenus = new();
[Mobius(
"""
App.TrayIconMenus
""")]
public IReadOnlyDictionary<string, ICommand> TrayIconMenus => trayIconMenus;
[Mobius(
"""
App.InitTrayIcon
""")]
void InitTrayIcon()
{
if (ApplicationLifetime is not
@@ -63,50 +83,14 @@ partial class App
#else
s.HasTrayIcon ? ShutdownMode.OnExplicitShutdown : ShutdownMode.OnMainWindowClose;
#endif
#if MACOS
if (Current!.TryGetFeature<IActivatableLifetime>() is { } activatableLifetime)
{
activatableLifetime.Activated += ActivatableLifetimeOnActivated;
// macOS 中 右键隐藏 会触发目前不需要使用
//activatableLifetime.Deactivated += ActivatableLifetimeOnDeactivated;
}
#endif
}
}
#if MACOS
/// <summary>
/// 从 macOS Dock 栏恢复窗口显示
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ActivatableLifetimeOnActivated(object? sender, ActivatedEventArgs e)
{
switch (e.Kind)
{
case ActivationKind.Background:
case ActivationKind.Reopen:
RestoreMainWindow();
break;
}
}
///// <summary>
///// macOS 中 右键隐藏 会触发目前不需要使用
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private void ActivatableLifetimeOnDeactivated(object? sender, ActivatedEventArgs e)
//{
// switch (e.Kind)
// {
// case ActivationKind.Background:
// Console.WriteLine($"ActivatableLifetimeOnDeactivated {e.Kind}");
// break;
// }
//}
#endif
[Mobius(
"""
App.UpdateMenuItems
""")]
public void UpdateMenuItems()
{
MainThread2.BeginInvokeOnMainThread(() =>
@@ -206,6 +190,10 @@ partial class App
});
}
[Mobius(
"""
App.UpdateMenuItems
""")]
public void UpdateMenuItems(string menuKey, TrayMenuItem trayMenuItem)
{
if (TrayMenus != null)
@@ -9,11 +9,6 @@
<StackPanel Spacing="10">
<HyperlinkButton Content="更新日志" />
<Button Content="更新日志" Theme="{StaticResource HyperlinkButton}" />
<Button Classes="TransparentAccent" Content="更新日志" />
<Button
Classes="TransparentAccent"
Content="更新日志"
IsEnabled="False" />
<Button
Content="更新日志"
IsEnabled="False"
@@ -226,15 +221,6 @@
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.TransparentAccent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkButtonForeground}" />
</Style>
<Style Selector="Button.TransparentAccent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button.TransparentAccent:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource HyperlinkButtonForegroundPressed}" />
</Style>
</Styles>
@@ -14,8 +14,6 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
public static readonly StyledProperty<bool> IsSaveWindowSizeProperty = AvaloniaProperty
.Register<ReactiveAppWindow<TViewModel>, bool>(nameof(IsSaveWindowSize), true);
private bool _isInitialize;
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveAppWindow{TViewModel}"/> class.
/// </summary>
@@ -129,32 +127,27 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel>, IV
{
var primaryScreenBounds = screen.Bounds;
if (CanResize && !_isInitialize && !IsVisible)
if (CanResize && !IsVisible)
{
WindowStartupLocation = WindowStartupLocation.Manual;
var scalingOffset = 31 - ((screen.Scaling - 1) / 0.5); // appwindow偏移量
if (SizePosition.Width > 0 &&
primaryScreenBounds.Width >= SizePosition.Width)
Width = SizePosition.Width;
if (SizePosition.Height > 0 &&
primaryScreenBounds.Height >= SizePosition.Height)
Height = SizePosition.Height - scalingOffset;
Height = SizePosition.Height;
if (SizePosition.X > 0 && SizePosition.Y > 0)
{
var offsetY = (int)Math.Round(scalingOffset * screen.Scaling);
//var offsetY = scalingOffset;
var leftTopPoint = new PixelPoint(SizePosition.X, SizePosition.Y + offsetY);
var leftTopPoint = new PixelPoint(SizePosition.X, SizePosition.Y);
var rightBottomPoint = new PixelPoint(SizePosition.X + (int)Width, SizePosition.Y + (int)Height);
if (primaryScreenBounds.Contains(leftTopPoint) &&
primaryScreenBounds.Contains(rightBottomPoint))
{
Position = leftTopPoint;
WindowStartupLocation = WindowStartupLocation.Manual;
}
}
_isInitialize = true;
}
}
}
@@ -74,18 +74,13 @@
MaxWidth="1080"
Spacing="20">
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{spp:OnPlatform !GA}"
Message="当前使用的程序版本为测试版本,可能会有各种各样不可言明的 Bug,遇到问题请理性反馈,谢谢。"
Severity="Warning" />
<spp:Snackbar
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
IsVisible="{spp:OnPlatform !GA}"
Message="如果您不喜欢当前主页为默认启动页面可以在【设置-界面设置-启动时默认打开页面】处更改" />
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top"
@@ -218,7 +218,6 @@
<TextBlock Text="{Binding Path=User.NickName, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
<Border
Padding="5,1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
CornerRadius="2"
IsVisible="{Binding Path=User.UserType, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:UserType.Sponsor}}">
@@ -341,7 +341,6 @@
<ComboBox SelectedItem="{Binding Source={x:Static ms:UISettings.WindowBackgroundMaterial}, Path=Value, Mode=TwoWay}">
<ComboBox.Items>
<spp:WindowBackgroundMaterial>None</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>Transparent</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>Blur</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>AcrylicBlur</spp:WindowBackgroundMaterial>
<spp:WindowBackgroundMaterial>Mica</spp:WindowBackgroundMaterial>
@@ -62,7 +62,7 @@ public sealed partial class MainWindow : ReactiveAppWindow<MainWindowViewModel>
pageType = page.PageType;
}
}
INavigationService.Instance.Navigate(pageType ?? typeof(HomePage));
INavigationService.Instance.Navigate(pageType ?? typeof(PluginStorePage));
return true;
}
@@ -0,0 +1,17 @@
namespace BD;
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class MobiusAttribute : Attribute
{
public MobiusAttribute()
{
}
public MobiusAttribute(string str)
{
}
public bool Obsolete { get; set; }
}
@@ -6,6 +6,7 @@ namespace BD.WTTS.Services;
/// <summary>
/// 子进程的 IPC 服务实现
/// </summary>
[Mobius(Obsolete = true)]
public interface IPCSubProcessService : IDisposable
{
/// <summary>
@@ -106,9 +106,9 @@
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<!--<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
<PackageReference Include="Aigio.WinDivertSharp" />
</ItemGroup>-->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ref\WTTS.MicroServices.ClientSDK\src\BD.WTTS.MicroServices.Primitives.Models\BD.WTTS.MicroServices.Primitives.Models.csproj" />
@@ -148,28 +148,28 @@ public static partial class ServiceCollectionExtensions
#if WINDOWS
///// <summary>
///// 注册数据包拦截器
///// </summary>
///// <param name="services"></param>
///// <returns></returns>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//internal static IServiceCollection AddPacketIntercept(this IServiceCollection services)
//{
// // https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/ServiceCollectionExtensions.cs#L21
// //services.AddSingleton<IDnsConflictSolver, HostsConflictSolver>();
// //services.AddSingleton<IDnsConflictSolver, ProxyConflictSolver>();
// services.TryAddSingleton<IDnsInterceptor, DnsInterceptor>();
// services.AddHostedService<DnsInterceptHostedService>();
/// <summary>
/// 注册数据包拦截器
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static IServiceCollection AddPacketIntercept(this IServiceCollection services)
{
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/ServiceCollectionExtensions.cs#L21
//services.AddSingleton<IDnsConflictSolver, HostsConflictSolver>();
//services.AddSingleton<IDnsConflictSolver, ProxyConflictSolver>();
services.TryAddSingleton<IDnsInterceptor, DnsInterceptor>();
services.AddHostedService<DnsInterceptHostedService>();
// services.AddSingleton<ITcpInterceptor, SshInterceptor>();
// services.AddSingleton<ITcpInterceptor, GitInterceptor>();
// services.AddSingleton<ITcpInterceptor, HttpInterceptor>();
// services.AddSingleton<ITcpInterceptor, HttpsInterceptor>();
// services.AddHostedService<TcpInterceptHostedService>();
services.AddSingleton<ITcpInterceptor, SshInterceptor>();
services.AddSingleton<ITcpInterceptor, GitInterceptor>();
services.AddSingleton<ITcpInterceptor, HttpInterceptor>();
services.AddSingleton<ITcpInterceptor, HttpsInterceptor>();
services.AddHostedService<TcpInterceptHostedService>();
// return services;
//}
return services;
}
#endif
}
@@ -0,0 +1,17 @@
namespace BD;
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class MobiusAttribute : Attribute
{
public MobiusAttribute()
{
}
public MobiusAttribute(string str)
{
}
public bool Obsolete { get; set; }
}
@@ -20,7 +20,7 @@ public partial interface IReverseProxyConfig
/// </summary>
/// <param name="domain"></param>
/// <returns></returns>
bool IsMatch(string url) => TryGetDomainConfig(url, out _);
bool IsMatch(string domain) => TryGetDomainConfig(domain, out _);
/// <summary>
/// 尝试获取域名配置
@@ -28,7 +28,7 @@ public partial interface IReverseProxyConfig
/// <param name="domain"></param>
/// <param name="value"></param>
/// <returns></returns>
bool TryGetDomainConfig(string url, [MaybeNullWhen(false)] out IDomainConfig value);
bool TryGetDomainConfig(string domain, [MaybeNullWhen(false)] out IDomainConfig value);
/// <summary>
/// 尝试获取脚本配置
@@ -36,7 +36,7 @@ public partial interface IReverseProxyConfig
/// <param name="domain"></param>
/// <param name="value"></param>
/// <returns></returns>
bool TryGetScriptConfig(string url, [MaybeNullWhen(false)] out IEnumerable<IScriptConfig> value);
bool TryGetScriptConfig(string domain, [MaybeNullWhen(false)] out IEnumerable<IScriptConfig> value);
/// <summary>
/// 尝试获取脚本内容
@@ -148,26 +148,26 @@ sealed class ReverseProxyConfig : IReverseProxyConfig
// return result;
//}
public bool TryGetDomainConfig(string url, [MaybeNullWhen(false)] out IDomainConfig value)
public bool TryGetDomainConfig(string domain, [MaybeNullWhen(false)] out IDomainConfig value)
{
//value = domainConfigCache.GetOrAdd(domain.Host, GetDomainConfig);
var uri = new UriBuilder(url).Uri;
var uri = new UriBuilder(domain).Uri;
domainConfigCache.TryGetValue(uri.Host, out value);
if (value != null)
return true;
value = GetDomainConfig(url);
value = GetDomainConfig(domain);
if (value == null)
return false;
domainConfigCache.TryAdd(uri.Host, value);
return true;
IDomainConfig? GetDomainConfig(string url)
IDomainConfig? GetDomainConfig(string domain)
{
var key = domainConfigs.Keys.FirstOrDefault(item => item.IsMatch(url));
var key = domainConfigs.Keys.FirstOrDefault(item => item.IsMatch(domain));
return key == null ? null : domainConfigs[key];
}
}
@@ -12,10 +12,10 @@ sealed class LifetimeHttpHandler : DelegatingHandler
public LifeTimeKey LifeTimeKey { get; }
public LifetimeHttpHandler(IDomainResolver domainResolver, IWebProxy webProxy, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
public LifetimeHttpHandler(IDomainResolver domainResolver, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
{
LifeTimeKey = lifeTimeKey;
InnerHandler = new ReverseProxyHttpClientHandler(lifeTimeKey.DomainConfig, domainResolver, webProxy);
InnerHandler = new ReverseProxyHttpClientHandler(lifeTimeKey.DomainConfig, domainResolver);
timer = new Timer(OnTimerCallback, deactivateAction, lifeTime, Timeout.InfiniteTimeSpan);
}
@@ -15,7 +15,7 @@ public class ReverseProxyHttpClient : HttpMessageInvoker
// = new(new ProductHeaderValue(Constants.HARDCODED_APP_NAME_NEW + "_" + DateTimeOffset.UtcNow.Ticks, ThisAssembly.Version));
public ReverseProxyHttpClient(IDomainConfig domainConfig, IDomainResolver domainResolver)
: this(new ReverseProxyHttpClientHandler(domainConfig, domainResolver, HttpNoProxy.Instance), disposeHandler: true)
: this(new ReverseProxyHttpClientHandler(domainConfig, domainResolver), disposeHandler: true)
{
}
@@ -7,14 +7,12 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
{
readonly IDomainConfig domainConfig;
readonly IDomainResolver domainResolver;
readonly IWebProxy webProxy;
readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
public ReverseProxyHttpClientHandler(IDomainConfig domainConfig, IDomainResolver domainResolver, IWebProxy webProxy)
public ReverseProxyHttpClientHandler(IDomainConfig domainConfig, IDomainResolver domainResolver)
{
this.domainConfig = domainConfig;
this.domainResolver = domainResolver;
this.webProxy = webProxy;
InnerHandler = CreateSocketsHttpHandler();
}
@@ -55,199 +53,12 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
{
Proxy = HttpNoProxy.Instance,
UseProxy = false,
PreAuthenticate = false,
UseCookies = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
ConnectCallback = ConnectCallback,
EnableMultipleHttp2Connections = true,
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8,
ResponseHeaderEncodingSelector = (_, _) => Encoding.UTF8,
};
private async ValueTask<Stream> ConnectThroughProxyAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
if (context.InitialRequestMessage.RequestUri == null)
{
throw new InvalidOperationException("Request URI is null");
}
var proxyUri = webProxy.GetProxy(context.InitialRequestMessage.RequestUri);
if (proxyUri == null)
{
throw new InvalidOperationException("Proxy URI is null");
}
var proxyEndPoint = new DnsEndPoint(proxyUri.Host, proxyUri.Port);
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(proxyEndPoint, cancellationToken);
var stream = new NetworkStream(socket, ownsSocket: true);
// 确定代理类型
var proxyType = DetermineProxyType(proxyUri);
switch (proxyType)
{
case ExternalProxyType.Http:
await ConnectHttpProxy(stream, context, cancellationToken);
break;
case ExternalProxyType.Socks4:
await ConnectSocks4Proxy(stream, context, cancellationToken);
break;
case ExternalProxyType.Socks5:
await ConnectSocks5Proxy(stream, context, cancellationToken);
break;
}
if (context.InitialRequestMessage.RequestUri.Scheme == "https" || context.InitialRequestMessage.RequestUri.Port == 443)
{
var requestContext = context.InitialRequestMessage.GetRequestContext();
var tlsSniValue = requestContext.TlsSniValue;
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false);
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
{
TargetHost = tlsSniValue.Value,
RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true,
}, cancellationToken);
return sslStream;
}
return stream;
}
private ExternalProxyType DetermineProxyType(Uri proxyUri)
{
return proxyUri.Scheme.ToLower() switch
{
"socks4" => ExternalProxyType.Socks4,
"socks5" => ExternalProxyType.Socks5,
_ => ExternalProxyType.Http
};
throw new NotImplementedException("Proxy type determination needs to be implemented");
}
private async Task ConnectHttpProxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var credentials = webProxy.Credentials as NetworkCredential;
var authHeader = credentials != null
? $"Proxy-Authorization: Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes($"{credentials.UserName}:{credentials.Password}"))}\r\n"
: string.Empty;
var connectRequest = $"CONNECT {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port} HTTP/1.1\r\n" +
$"Host: {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port}\r\n" +
$"{authHeader}\r\n";
var connectBytes = Encoding.ASCII.GetBytes(connectRequest);
await stream.WriteAsync(connectBytes, cancellationToken);
var buffer = new byte[1024];
var bytesRead = await stream.ReadAsync(buffer, cancellationToken);
var response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
if (!response.StartsWith("HTTP/1.1 200"))
{
throw new Exception($"Proxy connection failed: {response}");
}
}
private async Task ConnectSocks4Proxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var credentials = webProxy.Credentials as NetworkCredential;
var userId = credentials?.UserName ?? string.Empty;
var destinationAddress = await GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken).FirstAsync();
var portBytes = BitConverter.GetBytes((ushort)context.DnsEndPoint.Port);
if (BitConverter.IsLittleEndian) Array.Reverse(portBytes);
var request = new byte[] { 0x04, 0x01 }
.Concat(portBytes)
.Concat(destinationAddress.Address.GetAddressBytes())
.Concat(Encoding.ASCII.GetBytes(userId))
.Concat(new byte[] { 0x00 })
.ToArray();
await stream.WriteAsync(request, cancellationToken);
var response = new byte[8];
await stream.ReadAsync(response, cancellationToken);
if (response[1] != 0x5A)
{
throw new Exception($"SOCKS4 proxy connection failed: {response[1]}");
}
}
private async Task ConnectSocks5Proxy(NetworkStream stream, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var credentials = webProxy.Credentials as NetworkCredential;
// SOCKS5 握手
byte[] authMethod = credentials != null ? new byte[] { 0x02 } : new byte[] { 0x00 };
await stream.WriteAsync(new byte[] { 0x05, 0x01 }.Concat(authMethod).ToArray(), cancellationToken);
var response = new byte[2];
await stream.ReadAsync(response, cancellationToken);
if (response[0] != 0x05)
{
throw new Exception("SOCKS5 handshake failed");
}
// 如果需要身份验证
if (response[1] == 0x02 && credentials != null)
{
var userBytes = Encoding.ASCII.GetBytes(credentials.UserName);
var passBytes = Encoding.ASCII.GetBytes(credentials.Password);
var authRequest = new byte[] { 0x01, (byte)userBytes.Length }
.Concat(userBytes)
.Concat(new byte[] { (byte)passBytes.Length })
.Concat(passBytes)
.ToArray();
await stream.WriteAsync(authRequest, cancellationToken);
var authResponse = new byte[2];
await stream.ReadAsync(authResponse, cancellationToken);
if (authResponse[1] != 0x00)
{
throw new Exception("SOCKS5 authentication failed");
}
}
// 发送连接请求
byte[] addressBytes;
byte addressType;
if (IPAddress.TryParse(context.DnsEndPoint.Host, out IPAddress? ipAddress))
{
addressBytes = ipAddress.GetAddressBytes();
addressType = (byte)(ipAddress.AddressFamily == AddressFamily.InterNetwork ? 1 : 4);
}
else
{
addressBytes = Encoding.ASCII.GetBytes(context.DnsEndPoint.Host);
addressType = 3; // 域名
}
var request = new byte[] { 0x05, 0x01, 0x00, addressType }
.Concat(addressType == 3 ? new[] { (byte)addressBytes.Length } : Array.Empty<byte>())
.Concat(addressBytes)
.Concat(BitConverter.GetBytes((ushort)context.DnsEndPoint.Port).Reverse())
.ToArray();
await stream.WriteAsync(request, cancellationToken);
var connectResponse = new byte[10];
await stream.ReadAsync(connectResponse, cancellationToken);
if (connectResponse[1] != 0x00)
{
throw new Exception($"SOCKS5 connection failed: {connectResponse[1]}");
}
}
/// <summary>
/// 连接回调
/// </summary>
@@ -256,11 +67,6 @@ sealed class ReverseProxyHttpClientHandler : DelegatingHandler
/// <returns></returns>
async ValueTask<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
if (webProxy != null && !HttpNoProxy.IsNoProxy(webProxy))
{
return await ConnectThroughProxyAsync(context, cancellationToken);
}
var innerExceptions = new List<Exception>();
var ipEndPoints = GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken);
@@ -1,4 +1,3 @@
using System.Net;
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
// ReSharper disable once CheckNamespace
@@ -141,6 +140,11 @@ sealed class HttpLocalRequestMiddleware
return null;
}
StringValues cookie = context.Request.Headers["cookie-steamTool"];
if (StringValues.IsNullOrEmpty(cookie))
cookie = context.Request.Headers["Cookie"];
StringValues referer = context.Request.Headers["Referer-steamTool"];
context.Response.Headers.AccessControlAllowOrigin = context.Request.Headers.Origin.Count == 0 ? "*" : context.Request.Headers.Origin;
context.Response.Headers.AccessControlAllowHeaders = "*";
context.Response.Headers.AccessControlAllowMethods = "*";
@@ -162,30 +166,29 @@ sealed class HttpLocalRequestMiddleware
Method = methodObj,
Content = hasReqContent ? new StreamContent(context.Request.Body) : null,
};
foreach (var item in context.Request.Headers)
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
if (!StringValues.IsNullOrEmpty(cookie))
{
var headers = item.Key.ToLower();
if (headers.EndsWith("-steamtool"))
CookieHttpClient.CookieContainer.Add(new Cookie
{
CommentUri = requestUri,
Domain = requestUri.Host,
Value = cookie,
});
// req.Headers.Add("Cookie", cookie);
}
if (!StringValues.IsNullOrEmpty(referer))
{
try
{
var refererUri = new Uri(referer.ToString());
req.Headers.Referrer = refererUri;
}
catch
{
if (headers.Equals("cookie", StringComparison.OrdinalIgnoreCase))
{
CookieHttpClient.CookieContainer.Add(new Cookie
{
CommentUri = requestUri,
Domain = requestUri.Host,
Value = item.Value
});
}
if (headers.Equals("referer", StringComparison.OrdinalIgnoreCase))
{
var refererUri = new Uri(item.Value.ToString());
req.Headers.Referrer = refererUri;
}
req.Headers.TryAddWithoutValidation(item.Key.TrimEnd("-steamtool"), (IEnumerable<string?>)item.Value);
}
}
req.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
if (hasReqContent)
{
req.Content!.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType.ThrowIsNull());
@@ -1,6 +1,5 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.HttpServer/HttpReverseProxyMiddleware.cs
using Microsoft.AspNetCore.Http;
using Yarp.ReverseProxy.Forwarder;
// ReSharper disable once CheckNamespace
@@ -39,7 +38,6 @@ sealed partial class HttpReverseProxyMiddleware
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var url = context.Request.GetDisplayUrl();
//var url = context.Request.GetDisplayUrl().Remove(0, context.Request.Scheme.Length + 3);
var isScriptInject = reverseProxyConfig.TryGetScriptConfig(url, out var scriptConfigs);
@@ -52,31 +50,9 @@ sealed partial class HttpReverseProxyMiddleware
context.Response.Body = memoryStream;
}
if (TryGetDomainConfig(url.Remove(0, context.Request.Scheme.Length + 3), out var domainConfig) == false)
if (TryGetDomainConfig(url, out var domainConfig) == false)
{
if (reverseProxyConfig.Service.TwoLevelAgentEnable)
{
var httpClient = httpClientFactory.CreateHttpClient("GlobalProxy", defaultDomainConfig);
var destinationPrefix = GetDestinationPrefix(context.Request.Scheme, context.Request.Host, null);
var forwarderRequestConfig = new ForwarderRequestConfig()
{
Version = context.Request.Protocol switch
{
var protocol when protocol.StartsWith("HTTP/2") => System.Net.HttpVersion.Version20,
var protocol when protocol.StartsWith("HTTP/3") => System.Net.HttpVersion.Version30,
_ => System.Net.HttpVersion.Version11,
},
};
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, forwarderRequestConfig, HttpTransformer.Empty);
if (error != ForwarderError.None)
{
await HandleErrorAsync(context, error);
}
}
else
{
await next(context);
}
await next(context);
return;
}
@@ -182,7 +158,7 @@ sealed partial class HttpReverseProxyMiddleware
return true;
}
var host = new UriBuilder(uri).Host;
var host = new Uri(uri).Host;
// 未配置的域名,但仍然被解析到本机 IP 的域名
if (IsDomain(host))
{
@@ -53,14 +53,14 @@ sealed class DnsDohAnalysisService : GeneralHttpClientFactory
{
if (dnsClients.TryGetValue(dohAddresUri, out var value))
return value;
//var client = CreateClient($"{TAG}_{dohAddresUri}", HttpHandlerCategory.Default);
var handler = new HttpClientHandler
{
UseCookies = false,
UseProxy = false,
Proxy = HttpNoProxy.Instance,
};
var client = new HttpClient(handler);
var client = CreateClient($"{TAG}_{dohAddresUri}", HttpHandlerCategory.Default);
//var handler = new HttpClientHandler
//{
// UseCookies = false,
// UseProxy = false,
// Proxy = HttpNoProxy.Instance,
//};
//var client = new HttpClient(handler);
client.BaseAddress = dohAddresUri;
var dnsClient = new DnsHttpClient(client);
dnsClients.TryAdd(dohAddresUri, dnsClient);
@@ -1,203 +1,203 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Dns/DnsInterceptor.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Dns/DnsInterceptor.cs
//#if WINDOWS
#if WINDOWS
//using WinDivertSharp;
//using DNS.Protocol;
//using DNS.Protocol.ResourceRecords;
using WinDivertSharp;
using DNS.Protocol;
using DNS.Protocol.ResourceRecords;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <inheritdoc cref="IDnsInterceptor"/>
//sealed partial class DnsInterceptor : IDnsInterceptor, IDisposable
//{
// const string DNS_FILTER = "udp.DstPort == 53";
/// <inheritdoc cref="IDnsInterceptor"/>
sealed partial class DnsInterceptor : IDnsInterceptor, IDisposable
{
const string DNS_FILTER = "udp.DstPort == 53";
// readonly IReverseProxyConfig reverseProxyConfig;
// readonly ILogger<DnsInterceptor> logger;
readonly IReverseProxyConfig reverseProxyConfig;
readonly ILogger<DnsInterceptor> logger;
// readonly TimeSpan ttl = TimeSpan.FromMinutes(5d);
// bool disposedValue;
readonly TimeSpan ttl = TimeSpan.FromMinutes(5d);
bool disposedValue;
// /// <summary>
// /// 刷新 DNS 缓存
// /// </summary>
// [LibraryImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCache", SetLastError = true)]
// static partial void DnsFlushResolverCache();
/// <summary>
/// 刷新 DNS 缓存
/// </summary>
[LibraryImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCache", SetLastError = true)]
static partial void DnsFlushResolverCache();
// static DnsInterceptor()
// {
// // 首次加载驱动往往有异常,所以要提前加载
// var handle = WinDivert.WinDivertOpen("false", WinDivertLayer.Network, 0, WinDivertOpenFlags.None);
// WinDivert.WinDivertClose(handle);
// }
static DnsInterceptor()
{
// 首次加载驱动往往有异常,所以要提前加载
var handle = WinDivert.WinDivertOpen("false", WinDivertLayer.Network, 0, WinDivertOpenFlags.None);
WinDivert.WinDivertClose(handle);
}
// public DnsInterceptor(
// IReverseProxyConfig reverseProxyConfig,
// ILogger<DnsInterceptor> logger)
// {
// this.reverseProxyConfig = reverseProxyConfig;
// this.logger = logger;
// }
public DnsInterceptor(
IReverseProxyConfig reverseProxyConfig,
ILogger<DnsInterceptor> logger)
{
this.reverseProxyConfig = reverseProxyConfig;
this.logger = logger;
}
// public async Task InterceptAsync(CancellationToken cancellationToken)
// {
// await Task.Yield();
public async Task InterceptAsync(CancellationToken cancellationToken)
{
await Task.Yield();
// var handle = WinDivert.WinDivertOpen(DNS_FILTER, WinDivertLayer.Network, 0, WinDivertOpenFlags.None);
// if (handle == new IntPtr(unchecked((long)ulong.MaxValue)))
// {
// throw new Win32Exception();
// }
var handle = WinDivert.WinDivertOpen(DNS_FILTER, WinDivertLayer.Network, 0, WinDivertOpenFlags.None);
if (handle == new IntPtr(unchecked((long)ulong.MaxValue)))
{
throw new Win32Exception();
}
// cancellationToken.Register(hwnd =>
// {
// WinDivert.WinDivertClose((IntPtr)hwnd!);
// DnsFlushResolverCache();
// }, handle);
cancellationToken.Register(hwnd =>
{
WinDivert.WinDivertClose((IntPtr)hwnd!);
DnsFlushResolverCache();
}, handle);
// var packetLength = 0U;
// using WinDivertBuffer winDivertBuffer = new();
// WinDivertAddress winDivertAddress = default;
var packetLength = 0U;
using WinDivertBuffer winDivertBuffer = new();
WinDivertAddress winDivertAddress = default;
// DnsFlushResolverCache();
// while (!cancellationToken.IsCancellationRequested)
// {
// if (WinDivert.WinDivertRecv(handle, winDivertBuffer, ref winDivertAddress, ref packetLength) == false)
// {
// throw new Win32Exception();
// }
DnsFlushResolverCache();
while (!cancellationToken.IsCancellationRequested)
{
if (WinDivert.WinDivertRecv(handle, winDivertBuffer, ref winDivertAddress, ref packetLength) == false)
{
throw new Win32Exception();
}
// try
// {
// ModifyDnsPacket(winDivertBuffer, ref winDivertAddress, ref packetLength);
// }
// catch (Exception ex)
// {
// logger.LogWarning(ex.Message);
// }
// finally
// {
// WinDivert.WinDivertSend(handle, winDivertBuffer, packetLength, ref winDivertAddress);
// }
// }
// }
try
{
ModifyDnsPacket(winDivertBuffer, ref winDivertAddress, ref packetLength);
}
catch (Exception ex)
{
logger.LogWarning(ex.Message);
}
finally
{
WinDivert.WinDivertSend(handle, winDivertBuffer, packetLength, ref winDivertAddress);
}
}
}
// /// <summary>
// /// 修改 DNS 数据包
// /// </summary>
// /// <param name="winDivertBuffer"></param>
// /// <param name="winDivertAddress"></param>
// /// <param name="packetLength"></param>
// unsafe void ModifyDnsPacket(WinDivertBuffer winDivertBuffer, ref WinDivertAddress winDivertAddress, ref uint packetLength)
// {
// var packet = WinDivert.WinDivertHelperParsePacket(winDivertBuffer, packetLength);
// var requestPayload = new Span<byte>(packet.PacketPayload, (int)packet.PacketPayloadLength).ToArray();
/// <summary>
/// 修改 DNS 数据包
/// </summary>
/// <param name="winDivertBuffer"></param>
/// <param name="winDivertAddress"></param>
/// <param name="packetLength"></param>
unsafe void ModifyDnsPacket(WinDivertBuffer winDivertBuffer, ref WinDivertAddress winDivertAddress, ref uint packetLength)
{
var packet = WinDivert.WinDivertHelperParsePacket(winDivertBuffer, packetLength);
var requestPayload = new Span<byte>(packet.PacketPayload, (int)packet.PacketPayloadLength).ToArray();
// if (!TryParseRequest(requestPayload, out var request) ||
// request.OperationCode != OperationCode.Query ||
// request.Questions.Count == 0)
// {
// return;
// }
if (!TryParseRequest(requestPayload, out var request) ||
request.OperationCode != OperationCode.Query ||
request.Questions.Count == 0)
{
return;
}
// var question = request.Questions.First();
// if (question.Type != RecordType.A && question.Type != RecordType.AAAA)
// {
// return;
// }
var question = request.Questions.First();
if (question.Type != RecordType.A && question.Type != RecordType.AAAA)
{
return;
}
// var domain = question.Name;
// if (!reverseProxyConfig.IsMatch(question.Name.ToString()))
// {
// return;
// }
var domain = question.Name;
if (!reverseProxyConfig.IsMatch(question.Name.ToString()))
{
return;
}
// // DNS 响应数据
// var response = Response.FromRequest(request);
// var loopback = question.Type == RecordType.A ? IPAddress.Loopback : IPAddress.IPv6Loopback;
// var record = new IPAddressResourceRecord(domain, loopback, ttl);
// response.AnswerRecords.Add(record);
// var responsePayload = response.ToArray();
// DNS 响应数据
var response = Response.FromRequest(request);
var loopback = question.Type == RecordType.A ? IPAddress.Loopback : IPAddress.IPv6Loopback;
var record = new IPAddressResourceRecord(domain, loopback, ttl);
response.AnswerRecords.Add(record);
var responsePayload = response.ToArray();
// // 修改 payload 和包长
// responsePayload.CopyTo(new Span<byte>(packet.PacketPayload, responsePayload.Length));
// packetLength = (uint)((int)packetLength + responsePayload.Length - requestPayload.Length);
// 修改 payload 和包长
responsePayload.CopyTo(new Span<byte>(packet.PacketPayload, responsePayload.Length));
packetLength = (uint)((int)packetLength + responsePayload.Length - requestPayload.Length);
// // 修改 IP 包
// IPAddress destAddress;
// if (packet.IPv4Header != null)
// {
// destAddress = packet.IPv4Header->DstAddr;
// packet.IPv4Header->DstAddr = packet.IPv4Header->SrcAddr;
// packet.IPv4Header->SrcAddr = destAddress;
// packet.IPv4Header->Length = (ushort)packetLength;
// }
// else
// {
// destAddress = packet.IPv6Header->DstAddr;
// packet.IPv6Header->DstAddr = packet.IPv6Header->SrcAddr;
// packet.IPv6Header->SrcAddr = destAddress;
// packet.IPv6Header->Length = (ushort)(packetLength - sizeof(IPv6Header));
// }
// 修改 IP 包
IPAddress destAddress;
if (packet.IPv4Header != null)
{
destAddress = packet.IPv4Header->DstAddr;
packet.IPv4Header->DstAddr = packet.IPv4Header->SrcAddr;
packet.IPv4Header->SrcAddr = destAddress;
packet.IPv4Header->Length = (ushort)packetLength;
}
else
{
destAddress = packet.IPv6Header->DstAddr;
packet.IPv6Header->DstAddr = packet.IPv6Header->SrcAddr;
packet.IPv6Header->SrcAddr = destAddress;
packet.IPv6Header->Length = (ushort)(packetLength - sizeof(IPv6Header));
}
// // 修改 UDP 包
// (packet.UdpHeader->SrcPort, packet.UdpHeader->DstPort) = (packet.UdpHeader->DstPort, packet.UdpHeader->SrcPort);
// packet.UdpHeader->Length = (ushort)(sizeof(UdpHeader) + responsePayload.Length);
// 修改 UDP 包
(packet.UdpHeader->SrcPort, packet.UdpHeader->DstPort) = (packet.UdpHeader->DstPort, packet.UdpHeader->SrcPort);
packet.UdpHeader->Length = (ushort)(sizeof(UdpHeader) + responsePayload.Length);
// winDivertAddress.Impostor = true;
// winDivertAddress.Direction = winDivertAddress.Loopback
// ? WinDivertDirection.Outbound
// : WinDivertDirection.Inbound;
winDivertAddress.Impostor = true;
winDivertAddress.Direction = winDivertAddress.Loopback
? WinDivertDirection.Outbound
: WinDivertDirection.Inbound;
// WinDivert.WinDivertHelperCalcChecksums(winDivertBuffer, packetLength, ref winDivertAddress, WinDivertChecksumHelperParam.All);
WinDivert.WinDivertHelperCalcChecksums(winDivertBuffer, packetLength, ref winDivertAddress, WinDivertChecksumHelperParam.All);
// logger.LogInformation($"{domain} -> {loopback}");
// }
logger.LogInformation($"{domain} -> {loopback}");
}
// static bool TryParseRequest(byte[] payload, [NotNullWhen(true)] out Request? request)
// {
// try
// {
// request = Request.FromArray(payload);
// return true;
// }
// catch (Exception)
// {
// request = null;
// return false;
// }
// }
static bool TryParseRequest(byte[] payload, [NotNullWhen(true)] out Request? request)
{
try
{
request = Request.FromArray(payload);
return true;
}
catch (Exception)
{
request = null;
return false;
}
}
// private void Dispose(bool disposing)
// {
// if (!disposedValue)
// {
// if (disposing)
// {
// // TODO: 释放托管状态(托管对象)
// using var p = Process.Start(new ProcessStartInfo()
// {
// UseShellExecute = false,
// CreateNoWindow = true,
// FileName = "sc",
// Arguments = "stop WinDivert1.4",
// });
// }
private void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)
using var p = Process.Start(new ProcessStartInfo()
{
UseShellExecute = false,
CreateNoWindow = true,
FileName = "sc",
Arguments = "stop WinDivert1.4",
});
}
// // TODO: 释放未托管的资源(未托管的对象)并重写终结器
// // TODO: 将大型字段设置为 null
// disposedValue = true;
// }
// }
// TODO: 释放未托管的资源(未托管的对象)并重写终结器
// TODO: 将大型字段设置为 null
disposedValue = true;
}
}
// public void Dispose()
// {
// // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
// Dispose(disposing: true);
// GC.SuppressFinalize(this);
// }
//}
public void Dispose()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
//#endif
#endif
@@ -1,21 +1,21 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/GitInterceptor.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/GitInterceptor.cs
//#if WINDOWS
#if WINDOWS
//using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <summary>
///// Git 拦截器
///// </summary>
//sealed class GitInterceptor : TcpInterceptor
//{
// public GitInterceptor(ILogger<GitInterceptor> logger) : base(GitHubDesktopPort, GitPort, logger)
// {
/// <summary>
/// Git 拦截器
/// </summary>
sealed class GitInterceptor : TcpInterceptor
{
public GitInterceptor(ILogger<GitInterceptor> logger) : base(GitHubDesktopPort, GitPort, logger)
{
// }
//}
}
}
//#endif
#endif
@@ -1,21 +1,21 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/HttpInterceptor.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/HttpInterceptor.cs
//#if WINDOWS
#if WINDOWS
//using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <summary>
///// Http 拦截器
///// </summary>
//sealed class HttpInterceptor : TcpInterceptor
//{
// public HttpInterceptor(ILogger<HttpInterceptor> logger) : base(HttpPortDefault, HttpPort, logger)
// {
/// <summary>
/// Http 拦截器
/// </summary>
sealed class HttpInterceptor : TcpInterceptor
{
public HttpInterceptor(ILogger<HttpInterceptor> logger) : base(HttpPortDefault, HttpPort, logger)
{
// }
//}
}
}
//#endif
#endif
@@ -1,21 +1,21 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/HttpsInterceptor.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/HttpsInterceptor.cs
//#if WINDOWS
#if WINDOWS
//using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <summary>
///// Https 拦截器
///// </summary>
//sealed class HttpsInterceptor : TcpInterceptor
//{
// public HttpsInterceptor(ILogger<HttpsInterceptor> logger) : base(HttpsPortDefault, HttpsPort, logger)
// {
/// <summary>
/// Https 拦截器
/// </summary>
sealed class HttpsInterceptor : TcpInterceptor
{
public HttpsInterceptor(ILogger<HttpsInterceptor> logger) : base(HttpsPortDefault, HttpsPort, logger)
{
// }
//}
}
}
//#endif
#endif
@@ -1,20 +1,20 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/SshInterceptor.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/SshInterceptor.cs
//#if WINDOWS
#if WINDOWS
//using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <summary>
///// SSH 拦截器
///// </summary>
//sealed class SshInterceptor : TcpInterceptor
//{
// public SshInterceptor(ILogger<SshInterceptor> logger) : base(SshPortDefault, SshPort, logger)
// {
/// <summary>
/// SSH 拦截器
/// </summary>
sealed class SshInterceptor : TcpInterceptor
{
public SshInterceptor(ILogger<SshInterceptor> logger) : base(SshPortDefault, SshPort, logger)
{
// }
//}
//#endif
}
}
#endif
@@ -1,22 +1,22 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/TcpInterceptHostedService.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/TcpInterceptHostedService.cs
//#if WINDOWS
#if WINDOWS
//using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
using static BD.WTTS.Models.Abstractions.IReverseProxyConfig;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <summary>
///// TCP 拦截后台服务
///// </summary>
//sealed class TcpInterceptHostedService : InterceptHostedService
//{
// public TcpInterceptHostedService(
// IEnumerable<ITcpInterceptor> tcpInterceptors,
// ILogger<TcpInterceptHostedService> logger,
// IReverseProxyConfig reverseProxyConfig) : base(new Interceptors(tcpInterceptors), logger, reverseProxyConfig)
// {
// }
//}
//#endif
/// <summary>
/// TCP 拦截后台服务
/// </summary>
sealed class TcpInterceptHostedService : InterceptHostedService
{
public TcpInterceptHostedService(
IEnumerable<ITcpInterceptor> tcpInterceptors,
ILogger<TcpInterceptHostedService> logger,
IReverseProxyConfig reverseProxyConfig) : base(new Interceptors(tcpInterceptors), logger, reverseProxyConfig)
{
}
}
#endif
@@ -1,115 +1,115 @@
//// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/TcpInterceptor.cs
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.PacketIntercept/Tcp/TcpInterceptor.cs
//#if WINDOWS
#if WINDOWS
//using WinDivertSharp;
using WinDivertSharp;
//// ReSharper disable once CheckNamespace
//namespace BD.WTTS.Services.Implementation;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.Services.Implementation;
///// <inheritdoc cref="ITcpInterceptor"/>
//abstract class TcpInterceptor : ITcpInterceptor
//{
// readonly string filter;
// readonly ushort oldServerPort;
// readonly ushort newServerPort;
// readonly ILogger logger;
/// <inheritdoc cref="ITcpInterceptor"/>
abstract class TcpInterceptor : ITcpInterceptor
{
readonly string filter;
readonly ushort oldServerPort;
readonly ushort newServerPort;
readonly ILogger logger;
// public TcpInterceptor(int oldServerPort, int newServerPort, ILogger logger)
// {
// filter = $"loopback and (tcp.DstPort == {oldServerPort} or tcp.SrcPort == {newServerPort})";
// this.oldServerPort = (ushort)oldServerPort;
// this.newServerPort = (ushort)newServerPort;
// this.logger = logger;
// }
public TcpInterceptor(int oldServerPort, int newServerPort, ILogger logger)
{
filter = $"loopback and (tcp.DstPort == {oldServerPort} or tcp.SrcPort == {newServerPort})";
this.oldServerPort = (ushort)oldServerPort;
this.newServerPort = (ushort)newServerPort;
this.logger = logger;
}
// /// <summary>
// /// 拦截指定端口的数据包
// /// </summary>
// /// <param name="cancellationToken"></param>
// /// <returns></returns>
// /// <exception cref="Win32Exception"></exception>
// public async Task InterceptAsync(CancellationToken cancellationToken)
// {
// if (oldServerPort == newServerPort)
// {
// return;
// }
/// <summary>
/// 拦截指定端口的数据包
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Win32Exception"></exception>
public async Task InterceptAsync(CancellationToken cancellationToken)
{
if (oldServerPort == newServerPort)
{
return;
}
// await Task.Yield();
await Task.Yield();
// var handle = WinDivert.WinDivertOpen(filter, WinDivertLayer.Network, 0, WinDivertOpenFlags.None);
// if (handle == new IntPtr(unchecked((long)ulong.MaxValue)))
// {
// throw new Win32Exception();
// }
var handle = WinDivert.WinDivertOpen(filter, WinDivertLayer.Network, 0, WinDivertOpenFlags.None);
if (handle == new IntPtr(unchecked((long)ulong.MaxValue)))
{
throw new Win32Exception();
}
// if (Socket.OSSupportsIPv4)
// {
// logger.LogInformation($"{IPAddress.Loopback}:{oldServerPort} <=> {IPAddress.Loopback}:{newServerPort}");
// }
// if (Socket.OSSupportsIPv6)
// {
// logger.LogInformation($"{IPAddress.IPv6Loopback}:{oldServerPort} <=> {IPAddress.IPv6Loopback}:{newServerPort}");
// }
// cancellationToken.Register(hwnd => WinDivert.WinDivertClose((IntPtr)hwnd!), handle);
if (Socket.OSSupportsIPv4)
{
logger.LogInformation($"{IPAddress.Loopback}:{oldServerPort} <=> {IPAddress.Loopback}:{newServerPort}");
}
if (Socket.OSSupportsIPv6)
{
logger.LogInformation($"{IPAddress.IPv6Loopback}:{oldServerPort} <=> {IPAddress.IPv6Loopback}:{newServerPort}");
}
cancellationToken.Register(hwnd => WinDivert.WinDivertClose((IntPtr)hwnd!), handle);
// var packetLength = 0U;
// using WinDivertBuffer winDivertBuffer = new();
// WinDivertAddress winDivertAddress = default;
var packetLength = 0U;
using WinDivertBuffer winDivertBuffer = new();
WinDivertAddress winDivertAddress = default;
// while (!cancellationToken.IsCancellationRequested)
// {
// winDivertAddress.Reset();
// if (WinDivert.WinDivertRecv(handle, winDivertBuffer, ref winDivertAddress, ref packetLength) == false)
// {
// throw new Win32Exception();
// }
while (!cancellationToken.IsCancellationRequested)
{
winDivertAddress.Reset();
if (WinDivert.WinDivertRecv(handle, winDivertBuffer, ref winDivertAddress, ref packetLength) == false)
{
throw new Win32Exception();
}
// try
// {
// ModifyTcpPacket(winDivertBuffer, ref winDivertAddress, ref packetLength);
// }
// catch (Exception ex)
// {
// logger.LogWarning(ex.Message);
// }
// finally
// {
// WinDivert.WinDivertSend(handle, winDivertBuffer, packetLength, ref winDivertAddress);
// }
// }
// }
try
{
ModifyTcpPacket(winDivertBuffer, ref winDivertAddress, ref packetLength);
}
catch (Exception ex)
{
logger.LogWarning(ex.Message);
}
finally
{
WinDivert.WinDivertSend(handle, winDivertBuffer, packetLength, ref winDivertAddress);
}
}
}
// /// <summary>
// /// 修改 TCP 数据端口的端口
// /// </summary>
// /// <param name="winDivertBuffer"></param>
// /// <param name="winDivertAddress"></param>
// /// <param name="packetLength"></param>
// unsafe void ModifyTcpPacket(WinDivertBuffer winDivertBuffer, ref WinDivertAddress winDivertAddress, ref uint packetLength)
// {
// var packet = WinDivert.WinDivertHelperParsePacket(winDivertBuffer, packetLength);
// if (packet.IPv4Header != null && packet.IPv4Header->SrcAddr.Equals(IPAddress.Loopback) == false)
// {
// return;
// }
// if (packet.IPv6Header != null && packet.IPv6Header->SrcAddr.Equals(IPAddress.IPv6Loopback) == false)
// {
// return;
// }
/// <summary>
/// 修改 TCP 数据端口的端口
/// </summary>
/// <param name="winDivertBuffer"></param>
/// <param name="winDivertAddress"></param>
/// <param name="packetLength"></param>
unsafe void ModifyTcpPacket(WinDivertBuffer winDivertBuffer, ref WinDivertAddress winDivertAddress, ref uint packetLength)
{
var packet = WinDivert.WinDivertHelperParsePacket(winDivertBuffer, packetLength);
if (packet.IPv4Header != null && packet.IPv4Header->SrcAddr.Equals(IPAddress.Loopback) == false)
{
return;
}
if (packet.IPv6Header != null && packet.IPv6Header->SrcAddr.Equals(IPAddress.IPv6Loopback) == false)
{
return;
}
// if (packet.TcpHeader->DstPort == oldServerPort)
// {
// packet.TcpHeader->DstPort = newServerPort;
// }
// else
// {
// packet.TcpHeader->SrcPort = oldServerPort;
// }
// winDivertAddress.Impostor = true;
// WinDivert.WinDivertHelperCalcChecksums(winDivertBuffer, packetLength, ref winDivertAddress, WinDivertChecksumHelperParam.All);
// }
//}
if (packet.TcpHeader->DstPort == oldServerPort)
{
packet.TcpHeader->DstPort = newServerPort;
}
else
{
packet.TcpHeader->SrcPort = oldServerPort;
}
winDivertAddress.Impostor = true;
WinDivert.WinDivertHelperCalcChecksums(winDivertBuffer, packetLength, ref winDivertAddress, WinDivertChecksumHelperParam.All);
}
}
//#endif
#endif
@@ -1,14 +1,11 @@
// https://github.com/dotnetcore/FastGithub/blob/2.1.4/FastGithub.Http/HttpClientFactory.cs
// ReSharper disable once CheckNamespace
using System.Net;
namespace BD.WTTS.Services.Implementation;
sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
{
readonly IDomainResolver domainResolver;
readonly IWebProxy webProxy;
/// <summary>
/// 首次生命周期
@@ -30,19 +27,9 @@ sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
/// </summary>
readonly ConcurrentDictionary<LifeTimeKey, Lazy<LifetimeHttpHandler>> httpHandlerLazyCache = new();
public ReverseProxyHttpClientFactory(IDomainResolver domainResolver, IReverseProxyConfig reverseProxyConfig)
public ReverseProxyHttpClientFactory(IDomainResolver domainResolver)
{
this.domainResolver = domainResolver;
if (reverseProxyConfig.Service.TwoLevelAgentEnable)
{
this.webProxy = new WebProxy($"{reverseProxyConfig.Service.TwoLevelAgentProxyType}://{reverseProxyConfig.Service.TwoLevelAgentIp}:{reverseProxyConfig.Service.TwoLevelAgentPortId}", true, null, null);
if (!string.IsNullOrEmpty(reverseProxyConfig.Service.TwoLevelAgentUserName) || !string.IsNullOrEmpty(reverseProxyConfig.Service.TwoLevelAgentPassword))
{
webProxy.Credentials = new NetworkCredential(reverseProxyConfig.Service.TwoLevelAgentUserName, reverseProxyConfig.Service.TwoLevelAgentPassword);
}
}
else
this.webProxy = HttpNoProxy.Instance;
}
public ReverseProxyHttpClient CreateHttpClient(string domain, IDomainConfig domainConfig)
@@ -56,7 +43,7 @@ sealed class ReverseProxyHttpClientFactory : IReverseProxyHttpClientFactory
}
LifetimeHttpHandler CreateLifetimeHttpHandler(LifeTimeKey lifeTimeKey, TimeSpan lifeTime)
=> new(domainResolver, webProxy, lifeTimeKey, lifeTime, OnLifetimeHttpHandlerDeactivate);
=> new(domainResolver, lifeTimeKey, lifeTime, OnLifetimeHttpHandlerDeactivate);
void OnLifetimeHttpHandlerDeactivate(LifetimeHttpHandler lifetimeHttpHandler)
{
@@ -164,13 +164,10 @@ abstract class ReverseProxyServiceImpl : IReverseProxySettings
{
if (string.IsNullOrWhiteSpace(reverseProxySettings.ProxyIp))
return StartProxyResultCode.Exception;
if (reverseProxySettings.ProxyMode == ProxyMode.Hosts)
var inUsePort = SocketHelper.IsUsePort(IPAddress.Parse(reverseProxySettings.ProxyIp!), 443);
if (inUsePort)
{
var inUsePort = SocketHelper.IsUsePort(IPAddress.Parse(reverseProxySettings.ProxyIp!), 443);
if (inUsePort)
{
return StartProxyResultCode.BindPortError;
}
return StartProxyResultCode.BindPortError;
}
}
@@ -23,12 +23,12 @@ partial class YarpReverseProxyServiceImpl
services.AddReverseProxyServer();
services.AddFlowAnalyze();
//#if WINDOWS
// if (ProxyMode == ProxyMode.DNSIntercept)
// {
// services.AddPacketIntercept();
// }
//#endif
#if WINDOWS
if (ProxyMode == ProxyMode.DNSIntercept)
{
services.AddPacketIntercept();
}
#endif
// !!! 不可将 IPCSubProcessService ipc 添加到 YarpHost 的 DI 容器中,因停止加速时执行释放会导致进程退出
//services.AddSingleton(_ => ipc);
@@ -80,19 +80,17 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
builder.WebHost.UseKestrel(options =>
{
options.AddServerHeader = false;
options.RequestHeaderEncodingSelector = _ => Encoding.UTF8;
options.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
options.NoLimit();
#if WINDOWS
#if !NET7_0_OR_GREATER
if (OperatingSystem2.IsWindows7())
{
//https://github.com/dotnet/aspnetcore/issues/22563
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
});
}
if (OperatingSystem2.IsWindows7())
{
//https://github.com/dotnet/aspnetcore/issues/22563
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
});
}
#endif
//options.ListenSshReverseProxy();
//options.ListenGitReverseProxy();
@@ -12,8 +12,8 @@ public partial interface IDnsAnalysisService // 公共常量定义
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 Dnspod_DohAddres = "https://1.12.12.12/dns-query";
const string Dnspod_DohAddres2 = "https://doh.pub/dns-query";
const string Dnspod_DohAddres = "https://1.12.12.12";
const string Dnspod_DohAddres2 = "https://doh.pub";
const string Dnspod_DohAddres3 = "https://120.53.53.53/dns-query";
const string Google_DohAddres = "https://dns.google";
@@ -124,7 +124,7 @@
</EmbeddedResource>
</ItemGroup>
<!--<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
<Compile Update="Properties\WinDivertResource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -134,12 +134,12 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>WinDivertResource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>-->
</ItemGroup>
<!--<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<Compile Remove="Properties\WinDivertResource.Designer.cs" />
<EmbeddedResource Remove="Properties\WinDivertResource.resx" />
</ItemGroup>-->
</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectDirectory)\..\XunYouSDK\**\*.cs">
@@ -1,7 +0,0 @@
namespace BD.WTTS.Enums;
public enum TransportProtocol
{
Tcp,
Udp,
}
@@ -1,152 +1,132 @@
//#if WINDOWS
//using WinDivertBinRes = BD.WTTS.Properties.WinDivertResource;
#if WINDOWS
using WinDivertBinRes = BD.WTTS.Properties.WinDivertResource;
//namespace Mobius.Helpers;
namespace Mobius.Helpers;
///// <summary>
///// 初始化 WinDivert 助手类
///// </summary>
//static class WinDivertInitHelper
//{
// const string libraryName = "WinDivert.dll";
/// <summary>
/// 初始化 WinDivert 助手类
/// </summary>
static class WinDivertInitHelper
{
const string libraryName = "WinDivert.dll";
// static readonly string? libraryPath;
// static readonly string? libraryDirPath;
// //static nint libraryIntPtr;
static readonly string? libraryPath;
static readonly string? libraryDirPath;
//static nint libraryIntPtr;
// ///// <summary>
// ///// 配置本机库路径解析
// ///// </summary>
// ///// <param name="libraryName"></param>
// ///// <param name="assembly"></param>
// ///// <param name="searchPath"></param>
// ///// <returns></returns>
// //public static nint DllImportResolver(string libraryName,
// // Assembly assembly,
// // DllImportSearchPath? searchPath)
// //{
// // switch (libraryName)
// // {
// // case WinDivertInitHelper.libraryName:
// // if (libraryIntPtr != default)
// // return libraryIntPtr;
// // break;
// // }
// // return default;
// //}
///// <summary>
///// 配置本机库路径解析
///// </summary>
///// <param name="libraryName"></param>
///// <param name="assembly"></param>
///// <param name="searchPath"></param>
///// <returns></returns>
//public static nint DllImportResolver(string libraryName,
// Assembly assembly,
// DllImportSearchPath? searchPath)
//{
// switch (libraryName)
// {
// case WinDivertInitHelper.libraryName:
// if (libraryIntPtr != default)
// return libraryIntPtr;
// break;
// }
// return default;
//}
// static bool isInitialize = false;
static bool isInitialize = false;
// /// <summary>
// /// 初始化 WinDivert
// /// </summary>
// public static async Task InitializeAsync()
// {
// (byte[] WinDivert_dll, byte[] WinDivert64_sys) data = default;
/// <summary>
/// 初始化 WinDivert
/// </summary>
public static async Task InitializeAsync()
{
if (libraryPath != null && libraryDirPath != null)
{
bool isWrite;
var libraryFileInfo = new FileInfo(libraryPath);
if (libraryFileInfo.Exists)
{
if (libraryFileInfo.Length > 20000000L) // 文件不可能大于 20 MB
{
libraryFileInfo.Delete();
isWrite = true;
}
else
{
var libraryBytes = await File.ReadAllBytesAsync(libraryPath);
var libraryHash = SHA256.HashData(libraryBytes);
var libraryHashRes = SHA256.HashData(WinDivertBinRes.WinDivert_dll);
if (libraryHash.SequenceEqual(libraryHashRes)) // 哈希值相同,不写入
{
isWrite = false;
}
else
{
libraryFileInfo.Delete();
isWrite = true;
}
}
}
else
{
isWrite = true;
}
// void GetData()
// {
// if (data != default)
// data = GetWinDivertBinRes();
// }
if (isWrite) // 检查文件不存在时写入
{
switch (RuntimeInformation.ProcessArchitecture)
{
//case Architecture.X86:
// break;
case Architecture.X64:
IOPath.DirCreateByNotExists(libraryDirPath);
var task1 = File.WriteAllBytesAsync(Path.Combine(libraryDirPath, "WinDivert64.sys"), WinDivertBinRes.WinDivert64_sys);
var task2 = File.WriteAllBytesAsync(libraryPath, WinDivertBinRes.WinDivert_dll);
await Task.WhenAll(task1, task2);
break;
}
}
}
// if (libraryPath != null && libraryDirPath != null)
// {
// bool isWrite;
// var libraryFileInfo = new FileInfo(libraryPath);
// if (libraryFileInfo.Exists)
// {
// if (libraryFileInfo.Length > 20000000L) // 文件不可能大于 20 MB
// {
// libraryFileInfo.Delete();
// isWrite = true;
// }
// else
// {
// GetData();
// var libraryBytes = await File.ReadAllBytesAsync(libraryPath);
// var libraryHash = SHA256.HashData(libraryBytes);
// var libraryHashRes = SHA256.HashData(data.WinDivert_dll!);
// if (libraryHash.SequenceEqual(libraryHashRes)) // 哈希值相同,不写入
// {
// isWrite = false;
// }
// else
// {
// libraryFileInfo.Delete();
// isWrite = true;
// }
// }
// }
// else
// {
// isWrite = true;
// }
if (isInitialize)
return;
isInitialize = true;
//if (IsSupported && libraryPath != null)
//{
// try
// {
// libraryIntPtr = NativeLibrary.Load(libraryPath);
// }
// catch
// {
// }
// if (libraryIntPtr != default)
// {
// NativeLibrary.SetDllImportResolver(typeof(WinDivertSharp.WinDivert).Assembly, DllImportResolver);
// return;
// }
//}
IsSupported = false;
}
// if (isWrite) // 检查文件不存在时写入
// {
// GetData();
// switch (RuntimeInformation.ProcessArchitecture)
// {
// //case Architecture.X86:
// // break;
// case Architecture.X64:
// IOPath.DirCreateByNotExists(libraryDirPath);
// var task1 = File.WriteAllBytesAsync(Path.Combine(libraryDirPath, "WinDivert64.sys"), data.WinDivert64_sys!);
// var task2 = File.WriteAllBytesAsync(libraryPath, data.WinDivert_dll!);
// await Task.WhenAll(task1, task2);
// break;
// }
// }
// }
/// <summary>
/// 当前运行环境是否支持使用 WinDivert
/// </summary>
public static bool IsSupported { get; private set; }
// if (isInitialize)
// return;
// isInitialize = true;
// //if (IsSupported && libraryPath != null)
// //{
// // try
// // {
// // libraryIntPtr = NativeLibrary.Load(libraryPath);
// // }
// // catch
// // {
// // }
// // if (libraryIntPtr != default)
// // {
// // NativeLibrary.SetDllImportResolver(typeof(WinDivertSharp.WinDivert).Assembly, DllImportResolver);
// // return;
// // }
// //}
// IsSupported = false;
// }
// /// <summary>
// /// 当前运行环境是否支持使用 WinDivert
// /// </summary>
// public static bool IsSupported { get; private set; }
// static WinDivertInitHelper()
// {
// switch (RuntimeInformation.OSArchitecture)
// {
// //case Architecture.X86:
// case Architecture.X64:
// IsSupported = true;
// libraryPath = GlobalDllImportResolver.GetLibraryPath("WinDivert.dll");
// libraryDirPath = Path.GetDirectoryName(libraryPath);
// break;
// }
// }
// static (byte[] WinDivert_dll, byte[] WinDivert64_sys) GetWinDivertBinRes()
// {
// byte[] bytes = WinDivertBinRes.WinDivert_mpo;
// bytes.AsSpan().Reverse();
// var data = MemoryPackSerializer.Deserialize<byte[][]>(bytes)!;
// return (data[0], data[1]);
// }
//}
//#endif
static WinDivertInitHelper()
{
switch (RuntimeInformation.OSArchitecture)
{
//case Architecture.X86:
case Architecture.X64:
IsSupported = true;
libraryPath = GlobalDllImportResolver.GetLibraryPath("WinDivert.dll");
libraryDirPath = Path.GetDirectoryName(libraryPath);
break;
}
}
}
#endif
@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
@@ -63,9 +63,19 @@ namespace BD.WTTS.Properties {
/// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// </summary>
internal static byte[] WinDivert_mpo {
internal static byte[] WinDivert_dll {
get {
object obj = ResourceManager.GetObject("WinDivert_mpo", resourceCulture);
object obj = ResourceManager.GetObject("WinDivert_dll", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// </summary>
internal static byte[] WinDivert64_sys {
get {
object obj = ResourceManager.GetObject("WinDivert64_sys", resourceCulture);
return ((byte[])(obj));
}
}
@@ -99,7 +99,10 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="WinDivert_mpo" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\..\ref\WinDivert.mpo;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<data name="WinDivert64_sys" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\..\ref\WinDivert64.sys;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="WinDivert_dll" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\..\ref\WinDivert.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>
@@ -1,932 +0,0 @@
using STUN.Client;
using STUN.StunResult;
using System.Net.NetworkInformation;
namespace BD.WTTS.Services.Implementation;
internal partial class NetworkTestService : INetworkTestService
{
#region 常量
// 默认测试地址
const string DEFAULT_TESTSERVER_HOSTNAME = "stun.syncthing.net";
const int DEFAULT_PORT = 3478;
const int DEFAULT_TLSPORT = 5349;
// 默认端口号
const int DEFAULT_TESTSTUN3489_PORT = DEFAULT_PORT;
// 默认端口号
const int DEFAULT_TESTSTUN5389_PORT = DEFAULT_PORT;
#endregion 常量
public NetworkTestService()
{
}
#region Ping
/// <summary>
/// 将 Internet 控制消息协议 (ICMP) 回显请求消息发送到远程计算机,并等待来自该计算机的 ICMP 回送答复消息。 有关 ICMP 消息的详细说明,请参阅 RFC 792,可从 https://www.ietf.org获取。
/// </summary>
/// <param name="testHostNameOrAddress"></param>
/// <param name="timeout"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<PingReply> TestPingAsync(string testHostNameOrAddress, TimeSpan timeout, CancellationToken cancellationToken = default)
{
using Ping p = new Ping();
var reply = await p.SendPingAsync(
testHostNameOrAddress,
timeout,
cancellationToken: cancellationToken
);
return reply;
}
#endregion Ping
#region Upload/Download Speed Test
public async Task<(bool success, double? rate)> TestUploadSpeedAsync(
string uploadServerUrl,
byte[] uploadBytes,
CancellationToken cancellationToken = default
)
{
using HttpClient testClient = new HttpClient();
return await TestUploadSpeedCoreAsync(
() => testClient,
BuildUploadContent,
uploadServerUrl,
uploadBytes,
cancellationToken
);
static MultipartFormDataContent BuildUploadContent(byte[] data)
{
MultipartFormDataContent cotent = new MultipartFormDataContent();
cotent.Add(new ByteArrayContent(data));
return cotent;
}
}
public async Task<(bool success, double? rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default)
{
using HttpClient testClient = new HttpClient();
return await TestDownloadSpeedCoreAsync(downloadUrl, () => testClient, cancellationToken);
}
/// <summary>
/// 测试上传速度
/// </summary>
/// <param name="httpClientFunc">自定义用于发送 http 请求的 client </param>
/// <param name="uploadContentFunc">自定义上传内容类型</param>
/// <param name="uploadServerUrl"></param>
/// <param name="uploadBytes"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
static async Task<(bool success, double? rate)> TestUploadSpeedCoreAsync(
Func<HttpClient> httpClientFunc,
Func<byte[], HttpContent> uploadContentFunc,
string uploadServerUrl,
byte[] uploadBytes,
CancellationToken cancellationToken = default
)
{
if (string.IsNullOrWhiteSpace(uploadServerUrl))
return (false, null);
var uploadContent = uploadContentFunc(uploadBytes);
try
{
var httpClient = httpClientFunc();
Stopwatch watch = Stopwatch.StartNew();
var resp = await httpClient.PostAsync(uploadServerUrl, uploadContent, cancellationToken);
resp.EnsureSuccessStatusCode();
watch.Stop();
return (true, GetRate(uploadBytes.Length, watch.Elapsed.TotalSeconds));
}
catch (Exception ex)
{
Log.Error(nameof(NetworkTestService), ex, "测试上传异常");
}
return (false, null);
}
/// <summary>
/// 测试下载速度
/// </summary>
/// <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)
{
var httpClient = httpClientFunc?.Invoke() ?? new HttpClient();
try
{
Stopwatch watch = Stopwatch.StartNew();
var resp = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
resp.EnsureSuccessStatusCode();
var bytesArray = await resp.Content.ReadAsByteArrayAsync(cancellationToken);
watch.Stop();
return (true, GetRate(bytesArray.Length, watch.Elapsed.TotalSeconds));
}
catch (Exception ex)
{
Log.Error(nameof(NetworkTestService), ex, "测试下载异常");
return (false, null);
}
finally
{
if (httpClientFunc == null)
httpClient.Dispose();
}
}
static double GetRate(long byteArrayLength, double seconds)
{
if (seconds == 0)
throw new ArgumentException("时间不能小于0", nameof(seconds));
long bits = byteArrayLength * 8;
double megabits = bits / 1_000_000.0; // 将比特转换为兆比特
return megabits / seconds; // 返回速率,单位是 Mbps
}
#endregion Upload/Download Speed Test
#region STUN 测试
#region RFC3489
StunClient3489? _stunClient;
/// <summary>
/// 测试 STUN 客户端 基于 RFC3489
/// </summary>
/// <param name="testServerHostName">测试服务地址</param>
/// <param name="testServerPort">测试服务端口</param>
/// <param name="localIPEndPoint">本机 IP </param>
/// <param name="force">强制刷新</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<ClassicStunResult?> TestStunClient3489Async(
string? testServerHostName = DEFAULT_TESTSERVER_HOSTNAME,
int? testServerPort = DEFAULT_TESTSTUN3489_PORT,
IPEndPoint? localIPEndPoint = null,
bool force = false,
CancellationToken cancellationToken = default
)
{
testServerHostName ??= DEFAULT_TESTSERVER_HOSTNAME;
testServerPort ??= DEFAULT_TESTSTUN3489_PORT;
if (_stunClient == null || force)
_stunClient = await GetStunClient3489Async(testServerHostName, testServerPort.Value, localIPEndPoint);
try
{
await _stunClient.QueryAsync(cancellationToken);
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
Log.Error(nameof(NetworkTestService), ex, "TestStunClient3489Async 异常");
return null;
}
return _stunClient.State;
}
private async ValueTask<StunClient3489> GetStunClient3489Async(string serverHostName, int port, IPEndPoint? localIPEndPoint = null)
{
IPEndPoint serverEndpoint = await GetServerIPEndPointAsync(serverHostName, port);
IPEndPoint localEndpoint = localIPEndPoint ?? new IPEndPoint(IPAddress.Any, default);
var client = new StunClient3489(
serverEndpoint,
localEndpoint
);
return client;
}
private async Task<IPEndPoint> GetServerIPEndPointAsync(string hostNameOrAddress, int port)
{
var serverIps = await Dns.GetHostAddressesAsync(hostNameOrAddress);
IPEndPoint serverEndpoint = new IPEndPoint(serverIps.First(), port);
return serverEndpoint;
}
#endregion RFC3489
#region RFC5389
/// <summary>
/// 测试 STUN 客户端 基于 RFC5389
/// </summary>
/// <param name="protocol"></param>
/// <param name="testServerHostName"></param>
/// <param name="testServerPort"></param>
/// <param name="localIPEndPoint"></param>
/// <param name="force"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<StunResult5389?> TestStunClient5389Async(
TransportProtocol protocol,
string? testServerHostName = DEFAULT_TESTSERVER_HOSTNAME,
int? testServerPort = DEFAULT_TESTSTUN5389_PORT,
IPEndPoint? localIPEndPoint = null,
CancellationToken cancellationToken = default
)
{
testServerHostName ??= DEFAULT_TESTSERVER_HOSTNAME;
testServerPort ??= DEFAULT_TESTSTUN5389_PORT;
IStunClient5389 client = await GetStunClient5389Async(protocol, testServerHostName, testServerPort.Value, localIPEndPoint);
if (client == null)
return null;
try
{
await client.QueryAsync(cancellationToken);
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
Log.Error(nameof(NetworkTestService), ex, "TestStunClient5389Async 异常");
return null;
}
return client.State;
}
private async ValueTask<IStunClient5389> GetStunClient5389Async(TransportProtocol protocol, string serverHostName, int port, IPEndPoint? localIPEndPoint = null)
{
IPEndPoint serverEndpoint = await GetServerIPEndPointAsync(serverHostName, port);
IPEndPoint localEndpoint = localIPEndPoint ?? new IPEndPoint(IPAddress.Any, default);
IStunClient5389 client = protocol == TransportProtocol.Tcp
? new StunClient5389TCP(serverEndpoint, localEndpoint)
: new StunClient5389UDP(serverEndpoint, localEndpoint);
return client;
}
#endregion RFC5389
#endregion STUN 测试
#region DNS 查询
/// <summary>
/// 测试 DNS UDP
/// </summary>
/// <param name="testDomain">要测试的域名</param>
/// <param name="dnsServerIp">DNS 服务地址</param>
/// <param name="dnsServerPort">DNS 服务端口</param>
/// <param name="dnsRecordType"> dns 测试类型 </param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<(long DelayMs, IPAddress[] Address)> TestDNSAsync(
string testDomain,
string dnsServerIp,
int dnsServerPort,
DnsQueryAnswerRecord.DnsRecordType dnsRecordType = DnsQueryAnswerRecord.DnsRecordType.A,
CancellationToken cancellationToken = default
)
{
using UdpClient udpClient = new UdpClient(dnsServerIp, dnsServerPort);
var query = new DnsQueryUdpRequest(testDomain, dnsRecordType);
Stopwatch watch = Stopwatch.StartNew();
await udpClient.SendAsync(query, cancellationToken);
var receivedData = await udpClient.ReceiveAsync(cancellationToken);
watch.Stop();
var dnsQueryResponse = new DnsQueryUdpResponse(receivedData.Buffer);
return (watch.ElapsedMilliseconds, dnsQueryResponse.GetAddresses().ToArray());
}
/// <summary>
/// 测试 DNS Over Https
/// </summary>
/// <param name="testDomain"></param>
/// <param name="dohServer"></param>
/// <param name="dnsRecordType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<(long DelayMs, IPAddress[] Address)> TestDNSOverHttpsAsync(
string testDomain,
string dohServer,
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);
string queryUrl = $"{dohServer}?name={testDomain}&type={dnsRecordType}";
Stopwatch watch = Stopwatch.StartNew();
HttpResponseMessage? resp = null;
try
{
resp = await client.GetAsync(queryUrl, HttpCompletionOption.ResponseContentRead, cancellationToken);
resp.EnsureSuccessStatusCode();
}
finally
{
watch.Stop();
}
var queryResp = await resp.Content.ReadFromJsonAsync<DnsQueryJsonResponse>();
return (watch.ElapsedMilliseconds, queryResp?.GetAddresses().ToArray() ?? Array.Empty<IPAddress>());
}
#region DNS 解析定义
internal interface IDnsQueryResponse
{
List<DnsQueryAnswerRecord> Answers { get; }
IEnumerable<IPAddress> GetAddresses();
}
internal record DnsQueryUdpResponse : DnsQueryUdpRequest, IDnsQueryResponse
{
public ushort AnswerCount { get; init; } // (ushort)((_origin[6] << 8) | _origin[7]);
public ushort AuthorityCount { get; init; } // (ushort)((_origin[8] << 8) | _origin[9]);
public ushort AdditionalCount { get; init; } // (ushort)((_origin[10] << 8) | _origin[11]);
public List<DnsQueryAnswerRecord> Answers { get; init; }
private readonly byte[] _origin;
public DnsQueryUdpResponse(byte[] origin) : base(origin)
{
_origin = origin;
AnswerCount = (ushort)((origin[Position++] << 8) | origin[Position++]);
AuthorityCount = (ushort)((origin[Position++] << 8) | origin[Position++]);
AdditionalCount = (ushort)((origin[Position++] << 8) | origin[Position++]);
// 跳过查询问题部分
for (int i = 0; i < QuestionCount; i++)
{
Position = SkipName(origin, Position);
Position += 4; // 跳过类型
}
Answers = new List<DnsQueryAnswerRecord>();
Position = ParseRecords(origin, Position, AnswerCount, Answers);
Position = ParseRecords(origin, Position, AuthorityCount, Answers);
Position = ParseRecords(origin, Position, AdditionalCount, Answers);
}
private static int ParseRecords(byte[] response, int position, int recordCount, List<DnsQueryAnswerRecord> answers)
{
for (int i = 0; i < recordCount; i++)
{
position = ParseName(response, position, out var parsedName);
int @type = (response[position++] << 8) | response[position++];
int @class = (response[position++] << 8) | response[position++];
long ttl = (response[position++] << 24) | (response[position++] << 16) | (response[position++] << 8) | response[position++];
int dataLength = (response[position++] << 8) | response[position++];
if (@type == (int)DnsQueryAnswerRecord.DnsRecordType.A && dataLength == 4) // Type A (IPv4)
{
var ipAddress = new byte[4];
Array.Copy(response, position, ipAddress, 0, 4);
answers.Add(
new DnsQueryAnswerRecord.A(
parsedName!,
(DnsQueryAnswerRecord.DnsRecordType)@type,
ttl,
new IPAddress(ipAddress)
)
);
}
else if (@type == (int)DnsQueryAnswerRecord.DnsRecordType.AAAA && dataLength == 16) // Type AAAA (IPv6)
{
var ipAddress = new byte[16];
Array.Copy(response, position, ipAddress, 0, 16);
answers.Add(
new DnsQueryAnswerRecord.AAAA(
parsedName!,
(DnsQueryAnswerRecord.DnsRecordType)@type,
ttl,
new IPAddress(ipAddress)
)
);
}
else if (type == (int)DnsQueryAnswerRecord.DnsRecordType.CNAME)
{
ParseName(response, position, out var parsedCName);
if (string.IsNullOrEmpty(parsedCName))
{
Log.Error(nameof(NetworkTestService), $"解析 DNS CName字段异常 name :{parsedName}, data length :{dataLength}");
continue;
}
answers.Add(
new DnsQueryAnswerRecord.CName(
parsedName!,
(DnsQueryAnswerRecord.DnsRecordType)@type,
ttl,
parsedCName!
)
);
}
else
{
Log.Error(nameof(NetworkTestService), $"未支持的 DNS响应记录 type :{@type} ,class :{@class} name :{parsedName}, data length :{dataLength}");
continue;
}
position += dataLength;
}
return position;
}
private static int ParseName(byte[] response, int position, out string? name)
{
StringBuilder nameBuilder = new StringBuilder();
int originalPosition = position;
bool isPointer = false;
// 0表示结束
while (response[position] != 0)
{
// dns 协议 名称可能是 指针 或者 直接表示
// 如果大于等于 192 表示是指针开始
if (response[position] >= 192)
{
if (!isPointer)
{
originalPosition = position + 2;
isPointer = true;
}
// 因为 00111111 可以忽略前两位 用 0x3F 掩码获取指针
int pointer = ((response[position] & 0x3F) << 8) | response[position + 1];
position = pointer;
}
else
{
int length = response[position];
position++;
nameBuilder.Append(Encoding.ASCII.GetString(response, position, length));
position += length;
if (response[position] != 0)
{
nameBuilder.Append('.');
}
}
}
if (!isPointer)
{
position++;
}
else
{
position = originalPosition;
}
name = nameBuilder.ToString();
return position;
}
private static int SkipName(byte[] response, int position)
{
if (response[position] >= 192)
{
return position + 2; // Name is a pointer
}
while (response[position] != 0)
{
position += response[position] + 1;
}
return position + 1;
}
public IEnumerable<IPAddress> GetAddresses()
{
foreach (var answer in Answers)
{
if (answer is DnsQueryAnswerRecord.A a)
{
yield return a.Data;
}
else if (answer is DnsQueryAnswerRecord.AAAA aaaa)
{
yield return aaaa.Data;
}
}
yield break;
}
}
internal record DnsQueryUdpRequest
{
public ushort QueryId { get; init; } // (ushort)((_origin[0] << 8) | _origin[1]);
public ushort Flags { get; init; } //(ushort)((_origin[2] << 8) | _origin[3]);
public ushort QuestionCount { get; init; } // (ushort)((_origin[4] << 8) | _origin[5]);
public int Length => _origin.Length;
protected int Position { get; set; }
private readonly byte[] _origin;
public DnsQueryUdpRequest(byte[] origin)
{
_origin = origin;
Position = default;
QueryId = (ushort)((origin[Position++] << 8) | origin[Position++]);
Flags = (ushort)((origin[Position++] << 8) | origin[Position++]);
QuestionCount = (ushort)((origin[Position++] << 8) | origin[Position++]);
}
public DnsQueryUdpRequest(string domainName, DnsQueryAnswerRecord.DnsRecordType type = DnsQueryAnswerRecord.DnsRecordType.A)
: this(CreateDnsQuery(domainName, type))
{
}
public static byte[] CreateDnsQuery(string domainName, DnsQueryAnswerRecord.DnsRecordType type = DnsQueryAnswerRecord.DnsRecordType.A)
{
// 12字节报文头部
const int DNS_QUERY_HEADER_BYTECOUNT = 12;
// 2字节终止符长度
const int DNS_QUERY_END_BYTECOUNT = 2;
// 4字节查询类型和查询类字段
const int DNS_QUERY_QUERYTYPE_BYTECOUNT = 4;
// dns queryId 范围是一个16位字段,16位无符号的范围就是0-65535
var queryId = (ushort)Random.Shared.Next(0, 65536);
byte[] dnsQuery = new byte[
DNS_QUERY_HEADER_BYTECOUNT
+ domainName.Length
+ DNS_QUERY_END_BYTECOUNT
+ DNS_QUERY_QUERYTYPE_BYTECOUNT
];
BuildQueryHeader(queryId, dnsQuery);
BuildBody(domainName, dnsQuery);
return dnsQuery;
void BuildQueryHeader(ushort queryId, byte[] query)
{
// 查询 ID 是一个 16 位(2 字节)的字段。
// 为了将这个 16 位的整数值分配到字节数组 dnsQuery 的两个字节中,
// 我们需要将其高 8 位和低 8 位分别存储在数组的第一个和第二个字节中
query[0] = (byte)(queryId >> 8);
query[1] = (byte)(queryId & 0xFF);
// 标志字段(Flags) 2 - 3 包含各种控制标志
// 如查询/响应(QR)、操作码(Opcode)、授权回答(AA)、截断(TC)、递归期望(RD)、递归可用(RA)等
query[2] = 0x01;
// 问题计数(QDCOUNT)4 - 5 表示查询问题部分的数量
query[5] = 0x01;
// 其他查询报文中通常为 0
// 回答记录计数(ANCOUNT),授权记录计数(NSCOUNT),附加记录计数(ARCOUNT)
}
void BuildBody(string domainName, byte[] query)
{
// 获取域名组成部分 因为后面要根据部分长度和内容填充结果
string[] domainParts = domainName.Split('.');
int currentPosition = 12;
// 根据 dns 查询报文 每一部分开始都要填充这一部分的长度
foreach (var domainPart in domainParts)
{
// 标志当前 part 的长度
query[currentPosition++] = (byte)domainPart.Length;
// 获取填充的字节
var fillBytes = Encoding.ASCII.GetBytes(domainPart);
// 移动到当前查询体中
fillBytes.CopyTo(query, currentPosition);
// 移动当前填充开始位置
currentPosition += fillBytes.Length;
}
// 0 表示域名结束
query[currentPosition++] = 0x00;
// 填充 查询类型和查询类字段
query[currentPosition++] = 0x00;
query[currentPosition++] = (byte)type; // 表示查询类型为 A 记录(IPv4 地址)。
query[currentPosition++] = 0x00;
query[currentPosition++] = 0x01; // 表示查询类为 IN(Internet 类)。
}
}
public static implicit operator byte[](DnsQueryUdpRequest data)
{
return data._origin;
}
public static implicit operator ReadOnlyMemory<byte>(DnsQueryUdpRequest data)
{
return data._origin;
}
}
internal record DnsQueryJsonResponse : IDnsQueryResponse
{
[S_JsonProperty("Status")]
public int Status { get; init; }
[S_JsonProperty("TC")]
public bool TC { get; init; }
[S_JsonProperty("RD")]
public bool RD { get; init; }
[S_JsonProperty("RA")]
public bool RA { get; init; }
[S_JsonProperty("AD")]
public bool AD { get; init; }
[S_JsonProperty("CD")]
public bool CD { get; init; }
[S_JsonProperty("Answer")]
public List<DnsQueryAnswerRecord> Answers { get; init; }
public DnsQueryJsonResponse(
int status,
bool tc,
bool rd,
bool ra,
bool ad,
bool cd,
List<DnsQueryAnswerRecord>? answers
)
{
Status = status;
TC = tc;
RD = rd;
RA = ra;
AD = ad;
CD = cd;
Answers = answers?.ToList() ?? new();
}
public IEnumerable<IPAddress> GetAddresses()
{
return Answers.Aggregate(Enumerable.Empty<IPAddress>(), (acc, x) =>
{
if (x is DnsQueryAnswerRecord.A ipv4)
{
return acc.Append(ipv4.Data);
}
else if (x is DnsQueryAnswerRecord.AAAA ipv6)
{
return acc.Append(ipv6.Data);
}
return acc;
});
}
}
class DnsQueryAnswerRecordConverter : System.Text.Json.Serialization.JsonConverter<DnsQueryAnswerRecord>
{
public override DnsQueryAnswerRecord? Read(ref Utf8JsonReader reader, Type typeToConvert, SystemTextJsonSerializerOptions options)
{
if (typeToConvert == typeof(DnsQueryAnswerRecord))
{
if (reader.TokenType == JsonTokenType.StartObject)
{
var json = JsonDocument.ParseValue(ref reader);
if (json.RootElement.TryGetProperty("type", out var typeElement))
{
var type = typeElement.GetInt32();
if (type == (int)DnsQueryAnswerRecord.DnsRecordType.A)
{
return json.RootElement.Deserialize<DnsQueryAnswerRecord.A>();
}
else if (type == (int)DnsQueryAnswerRecord.DnsRecordType.AAAA)
{
return json.RootElement.Deserialize<DnsQueryAnswerRecord.AAAA>();
}
else if (type == (int)DnsQueryAnswerRecord.DnsRecordType.CNAME)
{
return json.RootElement.Deserialize<DnsQueryAnswerRecord.CName>();
}
}
}
}
return null;
}
public override void Write(Utf8JsonWriter writer, DnsQueryAnswerRecord value, SystemTextJsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
[System.Text.Json.Serialization.JsonConverter(typeof(DnsQueryAnswerRecordConverter))]
[JsonDerivedType(typeof(DnsQueryAnswerRecord.A))]
[JsonDerivedType(typeof(DnsQueryAnswerRecord.AAAA))]
[JsonDerivedType(typeof(DnsQueryAnswerRecord.CName))]
internal record DnsQueryAnswerRecord
{
[S_JsonProperty("name")]
public string Name { get; set; }
[S_JsonProperty("type")]
public DnsRecordType Type { get; set; }
[S_JsonProperty("TTL")]
public long Ttl { get; set; }
public DnsQueryAnswerRecord(
string name,
DnsRecordType type,
long ttl
)
{
Name = name;
Type = @type;
Ttl = ttl;
//Data = data;
//DataLength = data.Length;
}
#region DnsQueryAnswerRecord 子类派生
internal record A : DnsQueryAnswerRecord
{
public A(string name, DnsRecordType type, long ttl, IPAddress data) : base(name, type, ttl)
{
Data = data;
}
[S_JsonProperty("data")]
[SystemTextJsonConverter(typeof(IPAddressConverter))]
public IPAddress Data { get; set; }
}
internal record AAAA : DnsQueryAnswerRecord
{
public AAAA(string name, DnsRecordType type, long ttl, IPAddress data) : base(name, type, ttl)
{
Data = data;
}
[S_JsonProperty("data")]
[SystemTextJsonConverter(typeof(IPAddressConverter))]
public IPAddress Data { get; set; }
}
internal record CName : DnsQueryAnswerRecord
{
public CName(string name, DnsRecordType type, long ttl, string data) : base(name, type, ttl)
{
Data = data;
}
[S_JsonProperty("data")]
public string Data { get; set; }
}
#endregion DnsQueryAnswerRecord 子类派生
class IPAddressConverter : System.Text.Json.Serialization.JsonConverter<IPAddress>
{
public override IPAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, SystemTextJsonSerializerOptions options)
{
IPAddress? result = null;
// 目前只解析字符串
if (reader.TokenType == JsonTokenType.String)
{
string? ipString = reader.GetString();
result = !string.IsNullOrWhiteSpace(ipString) ? IPAddress.Parse(ipString) : default;
}
// byte 数组支持
else if (reader.TokenType == JsonTokenType.StartArray)
{
List<byte> bytes = new List<byte>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
if (reader.TokenType == JsonTokenType.Number)
{
bytes.Add((byte)reader.GetInt32());
}
else if (reader.TokenType == JsonTokenType.String)
{
if (byte.TryParse(reader.GetString(), out byte byteValue))
{
bytes.Add(byteValue);
}
}
}
result = bytes.Count == 4 || bytes.Count == 16 ? new IPAddress(bytes.ToArray()) : default;
}
return result;
}
public override void Write(Utf8JsonWriter writer, IPAddress value, SystemTextJsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
internal enum DnsRecordClass : ushort
{
Internet = 1, // Internet(IN)
Chaos = 3, // Chaos(CH)
Hesiod = 4, // Hesiod(HS)
QCLASSNONE = 254, // QCLASS NONE
QCLASSANY = 255, // QCLASS * (ANY)
}
// 域名系统记录类型 (省略了有些未使用的类型)
internal enum DnsRecordType : byte
{
A = 1, // 指定域名对应的IPv4地址
AAAA = 28, // 指定域名对应的IPv6地址
CNAME = 5, // 别名记录,
MX = 15, // 邮件交换记录,
NS = 2, // 指定该域名由哪个DNS服务器来进行解析
TXT = 16, // 主机名或域名的说明
SOA = 6, // 起始授权机构
PTR = 12, // 反向IP查询
ANY = 255, // 所有DNS记录类型
}
}
#endregion DNS 解析定义
#endregion DNS 查询
}
@@ -1,111 +0,0 @@
using STUN.StunResult;
using System.Net.NetworkInformation;
using static BD.WTTS.Services.Implementation.NetworkTestService;
namespace BD.WTTS.Services;
internal interface INetworkTestService : IHttpRequestTestService, IStunTestService
{
/// <summary>
/// Ping
/// </summary>
/// <param name="testHostNameOrAddress"></param>
/// <param name="timeout"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PingReply> TestPingAsync(string testHostNameOrAddress, TimeSpan timeout, CancellationToken cancellationToken = default);
/// <summary>
/// 测试 DNS UDP
/// </summary>
/// <param name="testDomain"></param>
/// <param name="dnsServerIp"></param>
/// <param name="dnsServerPort"></param>
/// <param name="dnsRecordType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(long DelayMs, IPAddress[] Address)> TestDNSAsync(
string testDomain,
string dnsServerIp,
int dnsServerPort,
DnsQueryAnswerRecord.DnsRecordType dnsRecordType = DnsQueryAnswerRecord.DnsRecordType.A,
CancellationToken cancellationToken = default
);
/// <summary>
/// 测试 DNS Over Https
/// </summary>
/// <param name="testDomain"></param>
/// <param name="dohServer"></param>
/// <param name="dnsRecordType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(long DelayMs, IPAddress[] Address)> TestDNSOverHttpsAsync(
string testDomain,
string dohServer,
DnsQueryAnswerRecord.DnsRecordType dnsRecordType = DnsQueryAnswerRecord.DnsRecordType.A,
CancellationToken cancellationToken = default
);
}
internal interface IHttpRequestTestService
{
/// <summary>
/// 测试上传速度
/// </summary>
/// <param name="uploadServerUrl"></param>
/// <param name="uploadBytes"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(bool success, double? rate)> TestUploadSpeedAsync(
string uploadServerUrl,
byte[] uploadBytes,
CancellationToken cancellationToken = default
);
/// <summary>
/// 测试下载速度
/// </summary>
/// <param name="downloadUrl"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(bool success, double? rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default);
}
internal interface IStunTestService
{
/// <summary>
/// 测试 STUN Client RFC3489
/// </summary>
/// <param name="testServerHostName">STUN 服务地址</param>
/// <param name="testServerPort">STUN 服务端口</param>
/// <param name="localIPEndPoint">本机IP</param>
/// <param name="force">刷新测试 Client</param>
/// <remarks></remarks>
/// <returns></returns>
Task<ClassicStunResult?> TestStunClient3489Async(
string? testServerHostName = default,
int? testServerPort = default,
IPEndPoint? localIPEndPoint = default,
bool force = false,
CancellationToken cancellationToken = default
);
/// <summary>
/// 测试 STUN Client RFC5389
/// </summary>
/// <param name="protocol">使用的传输协议 Tcp,Udp</param>
/// <param name="testServerHostName">STUN 服务地址</param>
/// <param name="testServerPort">STUN 服务端口</param>
/// <param name="localIPEndPoint">本机IP</param>
/// <param name="force"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<StunResult5389?> TestStunClient5389Async(
TransportProtocol protocol,
string? testServerHostName = default,
int? testServerPort = default,
IPEndPoint? localIPEndPoint = null,
CancellationToken cancellationToken = default
);
}
@@ -66,14 +66,7 @@ public sealed partial class GameAcceleratorService
DeleteMyGameCommand = ReactiveCommand.Create<XunYouGameViewModel>(DeleteMyGame);
GameAcceleratorCommand = ReactiveCommand.CreateFromTask<XunYouGameViewModel>(GameAccelerator);
GameLaunchCommand = ReactiveCommand.CreateFromTask<XunYouGameViewModel>(GameLaunch);
InstallAcceleratorCommand = ReactiveCommand.CreateFromTask(async () =>
{
if (await IWindowManager.Instance.ShowTaskDialogAsync(new MessageBoxWindowViewModel(),
pageContent: new AcceleratorPathAskBox(), title: "游戏加速", isCancelButton: false, isOkButton: false))
{
await InstallAccelerator();
}
});
InstallAcceleratorCommand = ReactiveCommand.CreateFromTask(InstallAccelerator);
UninstallAcceleratorCommand = ReactiveCommand.Create(UninstallAccelerator);
AcceleratorChangeAreaCommand = ReactiveCommand.Create<XunYouGameViewModel>(AcceleratorChangeArea);
@@ -355,7 +348,7 @@ public sealed partial class GameAcceleratorService
//}
if (!await IWindowManager.Instance.ShowTaskDialogAsync(new MessageBoxWindowViewModel(),
pageContent: new AcceleratorPathAskBox(), title: "游戏加速", isCancelButton: false, isOkButton: false))
pageContent: new AcceleratorPathAskBox(), title: "未安装加速插件", isCancelButton: true))
{
app.IsAccelerating = false;
return;
@@ -120,7 +120,6 @@ partial class ProxyService
switch (proxyMode)
{
case ProxyMode.DNSIntercept:
case ProxyMode.Hosts:
var inUsePort = SocketHelper.IsUsePort(proxyIp_.Value, httpsPort);
if (inUsePort)
@@ -187,13 +186,13 @@ partial class ProxyService
return Strings.CommunityFix_SetAsSystemPACProxyFail;
}
break;
//#if WINDOWS
// case ProxyMode.DNSIntercept:
// {
// await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
// }
// break;
//#endif
#if WINDOWS
case ProxyMode.DNSIntercept:
{
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
}
break;
#endif
}
if (!OperatingSystem.IsWindows())
@@ -207,7 +206,7 @@ partial class ProxyService
}
}
string? proxyToken = proxyDomains.Any_Nullable(s => s.ProxyType == ProxyType.ServerAccelerate || s.Items.Any_Nullable(x => x.ProxyType == ProxyType.ServerAccelerate)) ?
string? proxyToken = proxyDomains.Any_Nullable(s => s.ProxyType == ProxyType.ServerAccelerate) ?
await TryRequestServerSideProxyToken() : null;
ReverseProxySettings reverseProxySettings = new(proxyDomains, scripts,
@@ -316,7 +315,6 @@ partial class ProxyService
#endif
switch (proxyMode) // 先停止接入代理流量
{
case ProxyMode.DNSIntercept:
case ProxyMode.Hosts:
var needClear = hostsFileService.ContainsHostsByTag();
if (needClear)
@@ -353,14 +351,14 @@ partial class ProxyService
await platformService.SetAsSystemPACProxyAsync(false);
}
break;
//#if WINDOWS
// case ProxyMode.DNSIntercept:
// {
// // 停止时也调用初始化
// await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
// }
// break;
//#endif
#if WINDOWS
case ProxyMode.DNSIntercept:
{
// 停止时也调用初始化
await Mobius.Helpers.WinDivertInitHelper.InitializeAsync();
}
break;
#endif
}
StopTimer(); // 停止 UI 计时器
#if MACOS
@@ -1,7 +1,5 @@
// ReSharper disable once CheckNamespace
using Avalonia.Data;
using Google.Protobuf.WellKnownTypes;
using System.Linq;
namespace BD.WTTS.Services;
@@ -67,12 +65,12 @@ public sealed partial class ProxyService
.ToObservableChangeSet()
.AutoRefresh(x => x.ObservableItems)
.TransformMany(t => t.ObservableItems ?? new ObservableCollection<AccelerateProjectDTO>())
.AutoRefresh(x => x.ThreeStateEnable)
.WhenPropertyChanged(x => x.ThreeStateEnable, false)
.AutoRefresh(x => x.Checked)
.WhenPropertyChanged(x => x.Checked, false)
.Subscribe(_ =>
{
IsChangeSupportProxyServicesStatus = true;
ProxySettings.SupportProxyServicesStatus.Value = GetAccelerateEnableAllIds(EnableProxyDomains).ToImmutableHashSet();
ProxySettings.SupportProxyServicesStatus.Value = EnableProxyDomains?.Select(k => k.Id.ToString()).ToImmutableHashSet();
}));
this.WhenAnyValue(v => v.ProxyScripts)
@@ -154,10 +152,10 @@ public sealed partial class ProxyService
}
}
public IEnumerable<AccelerateProjectDTO> GetEnableProxyDomains()
public IEnumerable<AccelerateProjectDTO>? GetEnableProxyDomains()
{
if (!ProxyDomains.Items.Any_Nullable())
return [];
return null;
var data = ProxyDomains.Items
.Where(x => x.Items != null)
.SelectMany(s => s.Items!.Where(w => w.ThreeStateEnable != false));
@@ -165,7 +163,7 @@ public sealed partial class ProxyService
return data;
}
public IReadOnlyCollection<AccelerateProjectDTO>? EnableProxyDomains => GetEnableProxyDomains().ToImmutableArray();
public IReadOnlyCollection<AccelerateProjectDTO>? EnableProxyDomains => GetEnableProxyDomains()?.ToImmutableArray();
//static IEnumerable<AccelerateProjectDTO>? GetProxyDomainsItems(AccelerateProjectDTO accelerates)
//{
@@ -405,8 +403,22 @@ public sealed partial class ProxyService
{
var items = ProxyDomains.Items!.SelectMany(s => s.Items!);
var enableItems = ProxySettings.SupportProxyServicesStatus.Value;
RestoreAccelerateEnableAllIds(items, enableItems);
foreach (var item in items)
{
if (enableItems.Contains(item.Id.ToString()))
{
item.ThreeStateEnable = true;
}
}
}
//if (IsLoadImage && ProxyDomains.Items.Any_Nullable())
//{
// foreach (var item in ProxyDomains.Items)
// {
// item.ImageStream = ImageChannelType.AccelerateGroup.GetImageAsync(ImageUrlHelper.GetImageApiUrlById(item.ImageId));
// }
//}
}
public static bool IsChangeSupportProxyServicesStatus { get; set; }
@@ -450,29 +462,6 @@ public sealed partial class ProxyService
}
}
private IEnumerable<string> GetAccelerateEnableAllIds(IEnumerable<AccelerateProjectDTO>? nodes)
{
if (nodes == null)
return Enumerable.Empty<string>();
return nodes.Where(s => s.ThreeStateEnable == true).SelectMany(node => new[] { node.Id.ToString() }.Concat(GetAccelerateEnableAllIds(node.Items)));
}
private void RestoreAccelerateEnableAllIds(IEnumerable<AccelerateProjectDTO> nodes, IReadOnlyCollection<string> enableItems)
{
foreach (var node in nodes)
{
if (node.Items.Any_Nullable())
{
RestoreAccelerateEnableAllIds(node.Items, enableItems);
continue;
}
if (enableItems.Contains(node.Id.ToString()))
{
node.ThreeStateEnable = true;
}
}
}
Timer? timer;
public void StartTimer()
@@ -11,7 +11,7 @@ public static partial class ProxySettings
{
#if WINDOWS
yield return EProxyMode.Hosts;
//yield return EProxyMode.DNSIntercept;
yield return EProxyMode.DNSIntercept;
yield return EProxyMode.PAC;
yield return EProxyMode.System;
#elif ANDROID
@@ -11,8 +11,8 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
Title = DisplayName;
}
//public IEnumerable<ExternalProxyType> ProxyTypes { get; }
// = Enum2.GetAll<ExternalProxyType>();
public IEnumerable<ExternalProxyType> ProxyTypes { get; }
= Enum2.GetAll<ExternalProxyType>();
public IEnumerable<string> ProxyDNSs { get; } = new[]
{
@@ -10,14 +10,7 @@
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<StackPanel Spacing="15">
<TextBlock FontSize="21" TextAlignment="Center">
<Run Text="下载 Watt加速器" />
<LineBreak />
<Run Text="拒绝游戏延迟,提高稳定性" />
</TextBlock>
<StackPanel Spacing="6">
<ui:SettingsExpander
Description="{Binding Source={x:Static ms:GameAcceleratorSettings.WattAcceleratorDirPath}, Path=Value}"
Header="安装路径"
@@ -29,16 +22,6 @@
<Button Click="SelectWattAcceleratorInstallPath" Content="选择路径" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!--<TextBlock Text="需要下载 Watt 加速器插件才可使用,确定要下载吗?" />-->
<Button
Margin="0,15"
Padding="20,10"
HorizontalAlignment="Center"
Classes="TransparentAccent"
Click="OKButton_Click"
Content="立即下载"
FontSize="14" />
<TextBlock Text="需要下载 Watt 加速器插件才可使用,确定要下载吗?" />
</StackPanel>
</UserControl>
@@ -1,10 +1,9 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.ReactiveUI;
namespace BD.WTTS.UI.Views.Controls;
public partial class AcceleratorPathAskBox : ReactiveUserControl<MessageBoxWindowViewModel>
public partial class AcceleratorPathAskBox : UserControl
{
public AcceleratorPathAskBox()
{
@@ -31,9 +30,4 @@ public partial class AcceleratorPathAskBox : ReactiveUserControl<MessageBoxWindo
GameAcceleratorSettings.WattAcceleratorDirPath.Value = Path.Combine(path, "WattAccelerator");
}
}
private void OKButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
this.ViewModel?.Close?.Invoke(true);
}
}
@@ -531,23 +531,6 @@
<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>
@@ -754,7 +737,7 @@
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Styles>
<Button Theme="{StaticResource TransparentButton}" ToolTip.ShowDelay="0">
<Button Theme="{StaticResource TransparentButton}">
<ToolTip.Tip>
<TextBlock>
<Run FontSize="16" Text="Q:为什么加速后流量一直显示 0KB ?" />
@@ -767,7 +750,7 @@
<Run Text="A:可以尝试执行以下操作进行修复,关闭加速,点击加速设置-移除证书再安装证书,重开加速。或者加速设置-代理设置-尝试更换doh地址或者关闭doh使用dns等方案。" />
</TextBlock>
</ToolTip.Tip>
<ui:FontIcon FontSize="20" Glyph="&#xE82F;" />
<ui:FontIcon FontSize="22" Glyph="&#xE82F;" />
</Button>
<TextBlock Text="{x:Static spp:Strings.CommunityFix_ProxyMode}" />
<TextBlock Text="{Binding Source={x:Static ms:ProxySettings.ProxyMode}, Path=Value, Mode=OneWay, Converter={StaticResource EnumLocalizationNameConverter}}" />
@@ -35,22 +35,17 @@
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Description="{Binding Path=Res.Settings_Proxy_SystemProxyPortId_Tip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<TextBlock Text="{Binding Path=Res.Settings_Proxy_SystemProxyPortId, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<NumericUpDown
MaxWidth="200"
FormatString="0"
Maximum="65535"
Value="{Binding Source={x:Static ms:ProxySettings.SystemProxyPortId}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<!--<DockPanel>
<Label Content="监听端口"></Label>
<NumericUpDown Value="{Binding Source={x:Static ms:ProxySettings.HostProxyPortId},Path=Value, Mode=TwoWay}"
FormatString="0"
Maximum="65535"/>
</DockPanel>-->
<!--<DockPanel>
<TextBlock Text="IPV6支持:" />
<TextBlock Text="IPV6地址" ></TextBlock>
<Button Content="{Binding Path=Res.Refresh,Mode=OneWay,Source={x:Static s:ResourceService.Current}}"></Button>
<Button Content="{Binding Path=Res.Refresh,Mode=OneWay,Source={x:Static resx:R.Current}}"></Button>
</DockPanel>-->
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.CommunityFix_AutoRunProxy, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
@@ -94,72 +89,12 @@
<Button Content="选择证书"/>
</DockPanel>-->
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速完全不生效,请确认是配合二级代理实现仅需要脚本功能后再启用">
<ui:SettingsExpanderItem Description="*该选项勾选后会导致程序的加速完全不生效,请确认是仅需要脚本功能后再启用">
<TextBlock Text="{Binding Path=Res.CommunityFix_ProxyOnlyOnScript, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.OnlyEnableProxyScript}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:SettingsExpander
Description="*改动后需重启加速才能生效"
Header="{Binding Path=Res.Settings_Proxy_TwoLevelAgent, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
IsExpanded="True">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentEnable}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.Settings_Proxy_ProxyType, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<ui:FAComboBox MinWidth="200" SelectedItem="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentProxyType}, Path=Value, Mode=TwoWay}">
<ui:FAComboBox.Items>
<spp:ExternalProxyType>Http</spp:ExternalProxyType>
<spp:ExternalProxyType>Socks4</spp:ExternalProxyType>
<spp:ExternalProxyType>Socks5</spp:ExternalProxyType>
</ui:FAComboBox.Items>
</ui:FAComboBox>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.Settings_Proxy_IPAddress, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<TextBox MinWidth="200" Text="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentIp}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.Settings_Proxy_Port, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<NumericUpDown
MinWidth="200"
FormatString="0"
Maximum="65535"
Minimum="80"
Value="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentPortId}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.Settings_Proxy_UserName, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<TextBox MinWidth="200" Text="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentUserName}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding Path=Res.Settings_Proxy_Password, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:SettingsExpanderItem.Footer>
<TextBox
MinWidth="200"
Classes="revealPasswordButton"
PasswordChar="{StaticResource PasswordChar}"
Text="{Binding Source={x:Static ms:ProxySettings.TwoLevelAgentPassword}, Path=Value, Mode=TwoWay}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</UserControl>
@@ -24,7 +24,7 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
protected sealed override string? AuthorOriginalString => null;
public sealed override string Description => moduleName + " 控制台功能实现";
public sealed override string Description => moduleName;
public sealed override object? Icon => Resources.asf;
@@ -69,7 +69,7 @@ public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarm
isFirstStart = false;
}
(var isSuccess, var ipcUrl) = await StartProcessAsync();
(var isSuccess, var ipcUrl) = await StartProcess();
if (!isSuccess && !isFirstStart)
throw new ArgumentException();
else
@@ -85,7 +85,7 @@ public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarm
#region StartProcess
private async Task<(bool IsSuccess, string Message)> StartProcessAsync()
private async Task<(bool IsSuccess, string Message)> StartProcess()
{
var isStartSuccess = false;
var ipcUrl = string.Empty;
@@ -306,7 +306,7 @@ public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarm
}
}
public async Task RestartAsync()
public async Task Restart()
{
Toast.Show(ToastIcon.Info, AppResources.ASF_Restarting, ToastLength.Short);
@@ -47,7 +47,7 @@ public sealed class ASFService : ReactiveObject
{
SteamBotsSourceList = new SourceCache<BotViewModel, string>(t => t.Bot.BotName);
archiSteamFarmService.OnConsoleWirteLine += OnConsoleWriteLine;
archiSteamFarmService.OnConsoleWirteLine += OnConsoleWirteLine;
ASFSettings.ConsoleMaxLine.Subscribe(x =>
{
@@ -58,9 +58,9 @@ public sealed class ASFService : ReactiveObject
});
}
void OnConsoleWriteLine(string message)
void OnConsoleWirteLine(string message)
{
MainThread2.BeginInvokeOnMainThread(() =>
MainThread2.InvokeOnMainThreadAsync(() =>
{
//ConsoleLogBuilder.Append(message); // message 包含换行
//var text = ConsoleLogBuilder.ToString();
@@ -74,7 +74,7 @@ public sealed class ASFService : ReactiveObject
{
return;
}
_ = archiSteamFarmService.ShellMessageInput(input);
archiSteamFarmService.ShellMessageInput(input);
}
/// <summary>
@@ -287,7 +287,7 @@ public sealed class ASFService : ReactiveObject
Toast.Show(ToastIcon.Success, string.Format(AppResources.LocalAuth_ImportSuccessTip_, num));
}
public async Task SelectASFProgramLocationAsync()
public async Task SelectASFProgramLocation()
{
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
new("ArchiSteamFarm") {
@@ -302,77 +302,5 @@ public sealed class ASFService : ReactiveObject
ASFSettings.ArchiSteamFarmExePath.Value = path;
}, fileTypes);
}
public async void DownloadASFAsync(string variant = "win-x64", IProgress<float>? progress = null, CancellationToken cancellationToken = default)
{
try
{
var assetName = $"{nameof(ASF)}-{variant}.zip";
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", IHttpPlatformHelperService.Instance.UserAgent);
using var latestReleaseStream = await httpClient.GetStreamAsync("https://api.github.com/repos/JustArchiNET/ArchiSteamFarm/releases/latest", cancellationToken);
using var element = await JsonDocument.ParseAsync(latestReleaseStream, cancellationToken: cancellationToken);
if (element.RootElement.TryGetProperty("assets", out var assets) &&
assets.EnumerateArray().FirstOrDefault(s => s.GetProperty("name").ValueEquals(assetName))
.TryGetProperty("browser_download_url", out var downloadUrl))
{
var tag_name = element.RootElement.GetProperty("tag_name").GetString();
var downloadSavingPath = Path.Combine(Plugin.Instance.AppDataDirectory, $"ASF-{tag_name}", assetName);
var downloadSavingDir = Path.GetDirectoryName(downloadSavingPath)!;
Directory.CreateDirectory(downloadSavingDir);
var message = new HttpRequestMessage(HttpMethod.Get, downloadUrl.GetString());
var result = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
using var contentStream = await result.Content.ReadAsStreamAsync(cancellationToken);
byte batch = 0;
long readThisBatch = 0;
long batchIncreaseSize = result.Content.Headers.ContentLength.GetValueOrDefault() / 100;
await using (FileStream fileStream = new(downloadSavingPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
{
ArrayPool<byte> bytePool = ArrayPool<byte>.Shared;
byte[] buffer = bytePool.Rent(4096);
try
{
while (contentStream.CanRead)
{
int read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken);
if (read == 0)
break;
await fileStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
if ((progress == null) || (batchIncreaseSize == 0) || (batch >= 99))
{
continue;
}
readThisBatch += read;
while ((readThisBatch >= batchIncreaseSize) && (batch < 99))
{
readThisBatch -= batchIncreaseSize;
progress.Report(++batch);
}
}
}
finally
{
bytePool.Return(buffer);
}
}
ZipFile.ExtractToDirectory(downloadSavingPath, downloadSavingDir);
ASFSettings.ArchiSteamFarmExePath.Value = Path.Combine(downloadSavingDir, "ArchiSteamFarm.exe");
progress?.Report(100);
Toast.Show(ToastIcon.Success, "ASF 下载成功");
}
}
catch (Exception ex)
{
Toast.LogAndShowT(ex, "ASF 文件下载异常");
}
}
}
#endif
@@ -4,16 +4,8 @@ public partial class ArchiSteamFarmExePathSettingsPageViewModel : ViewModelBase
{
public ICommand SelectProgramPath { get; }
public ICommand DownloadASF { get; }
public ArchiSteamFarmExePathSettingsPageViewModel()
{
SelectProgramPath = ReactiveCommand.Create(ASFService.Current.SelectASFProgramLocationAsync);
DownloadASF = ReactiveCommand.Create(() =>
{
var progress = INotificationService.Instance.NotifyDownload(() => "开始下载 ASF ", NotificationType.NewVersion);
ASFService.Current.DownloadASFAsync(progress: progress);
Toast.Show(ToastIcon.Info, "等待后台下载文件", ToastLength.Short);
});
SelectProgramPath = ReactiveCommand.Create(ASFService.Current.SelectASFProgramLocation);
}
}
@@ -9,7 +9,7 @@ public sealed partial class ArchiSteamFarmPlusPageViewModel : ViewModelBase
public ArchiSteamFarmPlusPageViewModel()
{
SelectASFExePath = ReactiveCommand.CreateFromTask(ASFService.Current.SelectASFProgramLocationAsync);
SelectASFExePath = ReactiveCommand.CreateFromTask(ASFService.Current.SelectASFProgramLocation);
OpenWebUIConsole = ReactiveCommand.Create(() => ASFService.Current.OpenBrowser(null));
@@ -18,12 +18,12 @@ public sealed partial class ArchiSteamFarmPlusPageViewModel : ViewModelBase
RunOrStop = ReactiveCommand.Create(RunOrStopASF);
}
public override async void Activation()
public override void Activation()
{
base.Activation();
if (string.IsNullOrEmpty(ASFSettings.ArchiSteamFarmExePath.Value))
{
await MainThread2.InvokeOnMainThreadAsync(async () =>
MainThread2.InvokeOnMainThreadAsync(async () =>
{
var model = new ArchiSteamFarmExePathSettingsPageViewModel();
await IWindowManager.Instance.ShowTaskDialogAsync(model,
@@ -44,16 +44,7 @@ public sealed partial class ArchiSteamFarmPlusPageViewModel : ViewModelBase
StartOrStopASF_Click();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
ASFService.Current.StopASFAsync().Wait();
}
base.Dispose(disposing);
}
public void StartOrStopASF_Click(bool? startOrStop = null) => _ = Task.Run(async () =>
public static void StartOrStopASF_Click(bool? startOrStop = null) => Task.Run(async () =>
{
var s = ASFService.Current;
if (!s.IsASFRuning)
@@ -26,16 +26,5 @@
<Button Command="{Binding SelectProgramPath}" Content="选择路径" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
Description="下载并设置 ASF 路径"
Header="还没有下载过ASF?请点击下载"
IsClickEnabled="True">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE896;" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button Command="{Binding DownloadASF}" Content="一键下载" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</UserControl>
@@ -475,27 +475,6 @@ public static class AuthenticatorHelper
return filePath;
}
public static async Task<IEnumerable<IFileResult>?> MultipleSelectFolderPath(string? fileExtension = null)
{
var options = new PickOptions();
if (!string.IsNullOrEmpty(fileExtension))
{
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
new($"{fileExtension} Files") {
Patterns = new[] { $"*{fileExtension}", },
//MimeTypes
//AppleUniformTypeIdentifiers =
},
};
options.FileTypes = fileTypes;
}
var filePaths = await FilePicker2.PickMultipleAsync(options);
if (!filePaths.Any_Nullable())
Toast.Show(ToastIcon.Warning, Strings.FilePathNotExist);
return filePaths;
}
public static async Task<bool> VerifyMaxValue()
{
var auths = await AuthenticatorHelper.GetAllSourceAuthenticatorAsync();
@@ -1,6 +1,5 @@
using BD.WTTS.UI.Views.Pages;
using WinAuth;
using static SteamKit2.DepotManifest;
using SJsonSerializer = System.Text.Json.JsonSerializer;
namespace BD.WTTS.UI.ViewModels;
@@ -17,8 +16,6 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
{
AuthenticatorImportMethods = new List<AuthenticatorImportMethod>
{
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.LocalAuth_JoinSteamAuthenticator), Strings.LocalAuth_JoinSteamAuthenticatorDesc,
PageType: typeof(JoinSteamAuthenticatorPage)),
new AuthenticatorImportMethod(Strings.Auth_SteamLoginImport, Strings.Steam_UserLoginTip,
PageType: typeof(SteamLoginImportPage)),
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.SteamGuard), Strings.LocalAuth_SteamGuardImport,
@@ -29,6 +26,8 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
if (await AuthenticatorHelper.VerifyMaxValue())
await ImportFromSdaFile();
})),
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.LocalAuth_JoinSteamAuthenticator), Strings.LocalAuth_JoinSteamAuthenticatorDesc,
PageType: typeof(JoinSteamAuthenticatorPage)),
new AuthenticatorImportMethod(Strings.LocalAuth_Import.Format(Strings.WattToolKitV2), Strings.LocalAuth_WattToolKitV2Import,
Command: ReactiveCommand.Create(async () =>
{
@@ -69,40 +68,34 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
{
try
{
var filePaths = await AuthenticatorHelper.MultipleSelectFolderPath(FileEx.maFile);
var filePath = await AuthenticatorHelper.SelectFolderPath(FileEx.maFile);
if (!filePaths.Any_Nullable())
return;
if (string.IsNullOrEmpty(filePath)) return;
foreach (var file in filePaths)
var text = await File.ReadAllTextAsync(filePath);
var sdaFileModel = SJsonSerializer.Deserialize(text, ImportFileModelJsonContext.Default.SdaFileModel);
sdaFileModel.ThrowIsNull();
var steamDataModel = new SdaFileConvertToSteamDataModel(sdaFileModel);
SteamAuthenticator steamAuthenticator = new()
{
if (!File.Exists(file.FullPath)) return;
var text = await File.ReadAllTextAsync(file.FullPath);
DeviceId = sdaFileModel.DeviceId,
Serial = sdaFileModel.SerialNumber,
SecretKey = Convert.FromBase64String(sdaFileModel.SharedSecret),
SteamData = SJsonSerializer.Serialize(steamDataModel, ImportFileModelJsonContext.Default.SdaFileConvertToSteamDataModel),
};
var sdaFileModel = SJsonSerializer.Deserialize(text, ImportFileModelJsonContext.Default.SdaFileModel);
sdaFileModel.ThrowIsNull();
var steamDataModel = new SdaFileConvertToSteamDataModel(sdaFileModel);
SteamAuthenticator steamAuthenticator = new()
var authDto =
new AuthenticatorDTO()
{
DeviceId = sdaFileModel.DeviceId,
Serial = sdaFileModel.SerialNumber,
SecretKey = Convert.FromBase64String(sdaFileModel.SharedSecret),
SteamData = SJsonSerializer.Serialize(steamDataModel, ImportFileModelJsonContext.Default.SdaFileConvertToSteamDataModel),
Name = $"(Steam){steamAuthenticator.AccountName}",
Value = steamAuthenticator,
Created = DateTimeOffset.Now,
};
var authDto =
new AuthenticatorDTO()
{
Name = $"(Steam){steamAuthenticator.AccountName}",
Value = steamAuthenticator,
Created = DateTimeOffset.Now,
};
if (await AuthenticatorHelper.SaveAuthenticator(authDto))
{
Toast.Show(ToastIcon.Success, Strings.ModelContent_ImportSuccessful_.Format(authDto.Name));
}
if (await AuthenticatorHelper.SaveAuthenticator(authDto))
{
Toast.Show(ToastIcon.Success, Strings.ModelContent_ImportSuccessful_.Format(authDto.Name));
}
}
catch (Exception e)
@@ -1,7 +1,8 @@
using AppResources = BD.WTTS.Client.Resources.Strings;
using BD.SteamClient.Models;
using BD.SteamClient.Services;
using WinAuth;
using AppResources = BD.WTTS.Client.Resources.Strings;
using WSteamClient = WinAuth.SteamClient;
namespace BD.WTTS.UI.ViewModels;
@@ -72,61 +73,11 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
}
else
{
IsLogged = await CheckAndRefreshToken();
IsLogged = true;
await GetConfirmations(_steamClient);
}
}
public async ValueTask<bool> CheckAndRefreshToken()
{
IsLoading = true;
// 不具备检查条件 没有保持的会话信息
if (_steamClient == null)
return false;
if (_steamClient.Session == null || _steamAuthenticator == null)
return false;
try
{
// token 依旧有效 直接返回
if (!_steamClient.Session.IsAccessTokenExpired())
return true;
// 是否有 refresh token
string? refreshToken = _steamClient.Session?.RefreshToken;
if (string.IsNullOrWhiteSpace(refreshToken))
return false;
var session = _steamClient.Session;
// 刷新 access token (有效时间同样为一天)
await session!.RefreshAccessToken(_steamClient);
// 使用原先获取的具体派生类型 修改数据
_steamAuthenticator.SessionData = session.ToString();
// 重置当前 client
if (!ResetCurrentClient())
{
return false;
}
// 通过原始令牌引用保存信息
await AuthenticatorHelper.SaveAuthenticator(_authenticatorDto)
.ConfigureAwait(false);
return true;
}
finally
{
IsLoading = false;
}
}
public async Task Refresh()
{
if (_steamClient?.IsLoggedIn() == true)
@@ -210,30 +161,6 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
IsLoading = false;
}
bool ResetCurrentClient()
{
try
{
if (_steamAuthenticator == null)
return false;
_steamClient?.Dispose();
// 重新生成 client
_steamClient = _steamAuthenticator
.GetClient(ResourceService.GetCurrentCultureSteamLanguageName());
if (_steamClient == null)
return false;
}
catch (Exception)
{
return false;
}
return true;
}
async Task<IEnumerable<SteamTradeConfirmationModel>?> GetConfirmations(WSteamClient steamClient)
{
IsLoading = true;
@@ -387,6 +314,7 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
// Content = Strings.LocalAuth_AuthTrade_MessageBoxTip_.Format(statusText)
// }, statusText, isCancelButton: true, isDialog: false))
//{
Toast.Show(ToastIcon.Info, Strings.LocalAuth_AuthTrade_ConfirmTip_.Format(statusText));
if (!await ChangeTradeStatus(status, selectedList)) return;
@@ -233,7 +233,6 @@ public sealed partial class GameListPageViewModel : TabItemViewModel
if (result.IsOK())
{
Toast.Show(ToastIcon.Info, Strings.GameList_RuningWait);
NavAppToSteamView(app);
app.StartSteamAppProcess(SteamAppRunType.UnlockAchievement);
SteamConnectService.Current.RuningSteamApps.TryAdd(app.AppId, app);
}
@@ -72,9 +72,6 @@
<Compile Include="..\ImplicitUsings.NLog.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<Compile Include="..\ImplicitUsings.Steam.cs">
<LinkBase>Properties</LinkBase>
</Compile>
<Compile Include="..\ImplicitUsings.Plugins.cs">
<LinkBase>Properties</LinkBase>
</Compile>
@@ -95,9 +92,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="UI\Views\CsgoVacRepairPage.axaml.cs">
<DependentUpon>CsgoVacRepairPage.axaml</DependentUpon>
</Compile>
<Compile Update="UI\Views\GameToolsPage.axaml.cs">
<DependentUpon>GameToolsPage.axaml</DependentUpon>
</Compile>
@@ -1,153 +0,0 @@
using static System.Net.Mime.MediaTypeNames;
namespace BD.WTTS.UI.ViewModels;
public sealed partial class CsgoVacRepairPageViewModel : ViewModelBase
{
string _OutputString = string.Empty;
public string OutputString
{
get => _OutputString;
set => this.RaiseAndSetIfChanged(ref _OutputString, value);
}
[Reactive]
public bool Repairing { get; set; } = false;
#if WINDOWS
readonly ISteamService steamService = ISteamService.Instance;
string BatPath = Path.Combine(Plugin.Instance.AppDataDirectory, "BAT", $"CSGOVAC_REPAIR{FileEx.BAT}");
public async Task Repairs_Click()
{
try
{
Repairing = true;
var installPath = $"\"{Path.GetDirectoryName(steamService.SteamProgramPath)}\\bin\"";
await ExcuteBatCommand(installPath);
}
finally
{
Repairing = false;
}
}
public async Task ExcuteBatCommand(string arg)
{
OutputString = string.Empty;
if (!File.Exists(BatPath))
{
GenerateFixScript(BatPath);
}
using var p = new Process
{
StartInfo =
{
FileName = BatPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
UseShellExecute = false,
CreateNoWindow = true,
Arguments = arg,
Verb = "runas",
}
};
p.Start();
_ = ConsumeReader(p.StandardOutput);
_ = ConsumeReader(p.StandardError);
await p.WaitForExitAsync();
}
async Task ConsumeReader(TextReader reader)
{
string? text;
while ((text = await reader.ReadLineAsync()) != null)
{
OutHandle(text);
}
}
void GenerateFixScript(string path)
{
var dirPath = Path.GetDirectoryName(path);
if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath!);
using var stream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
stream.Write("""
@echo off
chcp 65001
goto enableservice
:steam
echo Info - Checking if Steam is launched.
tasklist | find /I "Steam.exe"
if errorlevel 1 goto closedstatus
if not errorlevel 1 goto killsteam
:killsteam
taskkill /F /IM Steam.exe
goto steamrepair
:closedstatus
echo Info - Not Started
goto steamrepair
:enableservice
sc config Netman start= AUTO
sc start Netman
sc config RasMan start= AUTO
sc start RasMan
sc config TapiSrv start= AUTO
sc start TapiSrv
sc config MpsSvc start= AUTO
sc start MpsSvc
netsh advfirewall set allprofiles state on
goto steam
:steamrepair
echo Info - ※^>^>^> 执行修复启动器服务项
cd /d %1
steamservice /install
ping -n 2 127.0.0.1>nul
echo.
steamservice /repair
ping -n 2 127.0.0.1>nul
echo Info - ※ 恢复DEP默认启动设置
bcdedit /deletevalue nointegritychecks
bcdedit /deletevalue loadoptions
bcdedit /debug off
bcdedit /deletevalue nx
echo Info - ※^>^>^> 重启 Steam
cd /d ..
start /high steam
ping -n 2 127.0.0.1>nul
sc config "Steam Client Service" start= AUTO
sc start "Steam Client Service"
echo Info - ※ 执行完毕
exit
"""u8);
stream.Flush();
stream.SetLength(stream.Position);
}
void OutHandle(string? msg)
{
if (!string.IsNullOrEmpty(msg))
{
OutputString += msg + Environment.NewLine;
if (msg.Contains("Add firewall exception failed for steamservice.exe", StringComparison.OrdinalIgnoreCase))
{
OutputString += "info - ※ 修复 Steam Services 失败\n";
OutputString += "info - ※ 请检查您的防火墙设置(关闭 \"不允许例外\" 选项)再次尝试\n";
}
}
}
#endif
}
@@ -123,59 +123,28 @@
Text="{Binding Path=Res.GameRelated_Borderless_MoveMouseTip, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</WrapPanel>
<Grid
<StackPanel
HorizontalAlignment="Center"
ColumnDefinitions="Auto,*"
IsVisible="{ReflectionBinding !!SelectWindow}"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Res.GameRelated_Borderless_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<TextBox
Grid.Column="1"
Margin="0,0,0,10"
IsReadOnly="True"
Text="{ReflectionBinding SelectWindow.Title,
Mode=OneWay}"
TextWrapping="WrapWithOverflow" />
<TextBlock
Grid.Row="1"
VerticalAlignment="Center"
Text="{Binding Path=Res.GameRelated_Borderless_Process, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,10"
IsReadOnly="True"
Text="{ReflectionBinding SelectWindow.Title,
Mode=OneWay}"
TextWrapping="WrapWithOverflow" />
<TextBlock
Grid.Row="2"
VerticalAlignment="Center"
Text="{Binding Path=Res.GameRelated_Borderless_ClassName, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<TextBox
Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,10"
IsReadOnly="True"
Text="{ReflectionBinding SelectWindow.Name,
Mode=OneWay}"
TextWrapping="WrapWithOverflow" />
<TextBlock
Grid.Row="3"
VerticalAlignment="Center"
Text="{Binding Path=Res.GameRelated_Borderless_Path, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<TextBox
Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,10"
IsReadOnly="True"
Text="{ReflectionBinding SelectWindow.Path,
Mode=OneWay}"
TextWrapping="WrapWithOverflow" />
</Grid>
Orientation="Vertical"
Spacing="10">
<TextBlock TextWrapping="WrapWithOverflow">
<Run Text="{Binding Path=Res.GameRelated_Borderless_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<Run Text="{ReflectionBinding SelectWindow.Title, Mode=OneWay}" />
</TextBlock>
<TextBlock TextWrapping="WrapWithOverflow">
<Run Text="{Binding Path=Res.GameRelated_Borderless_ClassName, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<Run Text="{ReflectionBinding SelectWindow.ClassName, Mode=OneWay}" />
</TextBlock>
<TextBlock TextWrapping="WrapWithOverflow">
<Run Text="{Binding Path=Res.GameRelated_Borderless_Process, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<Run Text="{ReflectionBinding SelectWindow.Name, Mode=OneWay}" />
</TextBlock>
<TextBlock TextWrapping="WrapWithOverflow">
<Run Text="{Binding Path=Res.GameRelated_Borderless_Path, Mode=OneWay, Source={x:Static s:ResourceService.Current}, StringFormat={}{0}:}" />
<Run Text="{ReflectionBinding SelectWindow.Path, Mode=OneWay}" />
</TextBlock>
</StackPanel>
<WrapPanel HorizontalAlignment="Center" Orientation="Horizontal">
<WrapPanel.Styles>
@@ -1,182 +0,0 @@
<spp:PageBase
x:Class="BD.WTTS.UI.Views.Pages.CsgoVacRepairPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="using:Avalonia.Xaml.Interactivity"
xmlns:ia="using:Avalonia.Xaml.Interactions.Core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:ui="using:FluentAvalonia.UI.Controls"
Title="CS:GO VAC 修复"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="spp:CsgoVacRepairPageViewModel"
Description="该功能可为玩家修复 CS:GO VAC 屏蔽问题"
Subtitle="{Binding Author, Source={x:Static s:Plugin.Instance}, Mode=OneWay, Converter={StaticResource StringFormatConverter}, ConverterParameter=Plugin_Author}"
mc:Ignorable="d">
<spp:PageBase.PreviewImage>
<ui:ImageIconSource Source="avares://BD.WTTS.Client.Plugins.GameTools/UI/Assets/CounterStrike.png" />
</spp:PageBase.PreviewImage>
<spp:PageBase.ActionContent>
<StackPanel Orientation="Horizontal" Spacing="2">
<Button
Padding="8,4"
DockPanel.Dock="Right"
IsVisible="{Binding !Repairing}"
Command="{ReflectionBinding Repairs_Click}"
Theme="{StaticResource TransparentButton}">
<Panel>
<Viewbox
Width="18"
Height="18"
HorizontalAlignment="Left">
<ui:FontIcon Glyph="&#xE90f;"/>
</Viewbox>
<TextBlock
Margin="28,4,0,4"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="修复" />
</Panel>
</Button>
<Button
Padding="8,4"
DockPanel.Dock="Right"
IsVisible="{Binding Repairing}"
Theme="{StaticResource TransparentButton}">
<Panel>
<Viewbox
Width="18"
Height="18"
HorizontalAlignment="Left">
<ui:FontIcon Glyph="&#xE895;"/>
</Viewbox>
<TextBlock
Margin="28,4,0,4"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="修复中" />
</Panel>
</Button>
<!--<ToggleButton
Padding="8,4"
DockPanel.Dock="Right"
IsChecked="{Binding $parent[spp:PageBase].IsPaneOpen, Mode=TwoWay}"
Theme="{StaticResource TransparentButton}">
<Panel>
<Viewbox
Width="18"
Height="18"
HorizontalAlignment="Left">
<ui:FontIcon Glyph="&#xEA80;" />
</Viewbox>
<TextBlock
Margin="28,4,0,4"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="帮助" />
</Panel>
</ToggleButton>-->
<DropDownButton
Padding="8"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{StaticResource More}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Theme="{StaticResource TransparentButton}">
<DropDownButton.Flyout>
<ui:FAMenuFlyout Placement="BottomEdgeAlignedRight">
<!--<ui:MenuFlyoutItem x:Name="PluginSettingButton" Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay,Source={x:Static s:ResourceService.Current}}" />
<ui:MenuFlyoutSeparator />-->
<ui:MenuFlyoutItem IsEnabled="False">
<ui:MenuFlyoutItem.Text>
<MultiBinding StringFormat="{}{0}{1}">
<CompiledBinding
Mode="OneWay"
Path="Res.Plugin_Version"
Source="{x:Static s:ResourceService.Current}" />
<CompiledBinding
Mode="OneWay"
Path="Version"
Source="{x:Static s:Plugin.Instance}" />
</MultiBinding>
</ui:MenuFlyoutItem.Text>
</ui:MenuFlyoutItem>
<!--<ui:MenuFlyoutItem Text="{Binding Path=Res.ViewInStore, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
<!--<ui:MenuFlyoutItem Text="{Binding Path=Res.UseHelp, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
<!--<ui:MenuFlyoutItem Text="{Binding Path=Res.AboutThisPlugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
</ui:FAMenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
</StackPanel>
</spp:PageBase.ActionContent>
<Panel>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid RowDefinitions="*,Auto">
<TextBox
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Classes="readonlybox"
Text="{Binding OutputString}"
TextAlignment="Start"
TextWrapping="Wrap" />
</Grid>
</Border>
</Panel>
<!--<spp:PageBase.PaneContent>
<StackPanel Margin="10,10" Spacing="10">
<TextBlock FontWeight="Bold" FontSize="13" Text ="其他 VAC 修复方式"/>
<spp:OptionsDisplayItem Description="" Header="验证游戏完整性">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xF156;" />
</spp:OptionsDisplayItem.Icon>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="关闭冲突软件">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xF167;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Command="{ReflectionBinding CloseConflictApp_Click}" Content="关闭" />
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="管理员启动Steam">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xEF58;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Command="{ReflectionBinding AdminStartup_Click}" Content="启动" />
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="重启电脑">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE7ad;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Command="{ReflectionBinding ResetComputer_Click}" Content="重启" />
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<spp:OptionsDisplayItem Header="修复系统文件">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE705;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Command="{ReflectionBinding RepairSystemFiles_Click}" Content="执行" />
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
<TextBlock FontWeight="Bold" FontSize="13" Text ="其他异常处理"/>
<spp:OptionsDisplayItem Header="闪退">
<spp:OptionsDisplayItem.Icon>
<ui:FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xea91;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<Button Content="修复" />
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
</StackPanel>
</spp:PageBase.PaneContent>-->
</spp:PageBase>
@@ -1,10 +0,0 @@
namespace BD.WTTS.UI.Views.Pages;
public partial class CsgoVacRepairPage : PageBase<CsgoVacRepairPageViewModel>
{
public CsgoVacRepairPage()
{
InitializeComponent();
DataContext ??= new CsgoVacRepairPageViewModel();
}
}
@@ -134,10 +134,8 @@
<Button
Click="GameToolsPage_Click"
IsEnabled="{spp:OnPlatform Windows}"
Tag="{x:Null}"
Theme="{StaticResource NavButtonStyle}">
<Button.Tag>
<x:Type TypeName="spp:CsgoVacRepairPage" />
</Button.Tag>
<Grid RowDefinitions="3*,*">
<Border
HorizontalAlignment="Stretch"
@@ -159,7 +157,7 @@
</Grid>
</Button>
</Border>
<Border spp:Animations.EnableAnimations="True">
<Button
Click="GameToolsPage_Click"
@@ -2,7 +2,6 @@ using BD.SteamClient.Models;
using BD.SteamClient.Models.Idle;
using BD.SteamClient.Services;
using BD.SteamClient.Constants;
using Avalonia.Threading;
namespace BD.WTTS.UI.ViewModels;
@@ -130,7 +129,13 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
}
else
{
StopIdleRun();
RunState = false;
StopIdle();
RunOrStopAutoNext(false);
RunOrStopAutoCardDropCheck(false);
IdleTime = default;
ResetCurrentIdle();
ChangeRunTxt();
}
RunLoaingState = false;
@@ -142,17 +147,6 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
}
}
private void StopIdleRun()
{
RunState = false;
StopIdle();
RunOrStopAutoNext(false);
RunOrStopAutoCardDropCheck(false);
IdleTime = default;
ResetCurrentIdle();
ChangeRunTxt();
}
/// <summary>
/// 手动切换下一个游戏
/// </summary>
@@ -313,26 +307,17 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
{
try
{
var isTokenAccess = Ioc.Get<ISteamAccountService>().IsAccessTokenValid(SteamLoginState.AccessToken!);
var isTokenAccess = await Ioc.Get<ISteamAccountService>().CheckAccessTokenValidation(SteamLoginState.AccessToken!);
if (isTokenAccess)
{
await RunLoadBadges();
}
else
{
var new_accessToken = await Ioc.Get<ISteamAccountService>().RefreshAccessToken(SteamLoginState.SteamId, SteamLoginState.RefreshToken!);
if (new_accessToken is not null) // 刷新Token
{
await RefreshAccessTokenAsync(new_accessToken);
await RunLoadBadges();
}
else // 重新登录
{
await ISecureStorage.Instance.RemoveAsync(ISteamSessionService.CurrentSteamUserKey);
SteamLoginState = new();
await LoginSteam();
await RunLoadBadges();
}
await ISecureStorage.Instance.RemoveAsync(ISteamSessionService.CurrentSteamUserKey);
SteamLoginState = new();
await LoginSteam();
await RunLoadBadges();
}
return true;
}
@@ -392,19 +377,17 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
{
try
{
IdleGameList.Clear();
var badges = Badges.Where(w => w.CardsRemaining != 0);
var apps = (SteamIdleSettings.IdleSequentital.Value switch
var apps = SteamIdleSettings.IdleSequentital.Value switch
{
IdleSequentital.LeastCards => badges.OrderBy(o => o.CardsRemaining).Select(s => new IdleApp(s)),
IdleSequentital.Mostcards => badges.OrderByDescending(o => o.CardsRemaining).Select(s => new IdleApp(s)),
IdleSequentital.Mostvalue => badges.OrderByDescending(o => o.RegularAvgPrice).Select(s => new IdleApp(s)),
_ => badges.Select(s => new IdleApp(s)),
}).ToImmutableList();
Dispatcher.UIThread.Invoke(() =>
{
IdleGameList.Clear();
EnumerableExtensions.AddRange(IdleGameList, apps);
});
};
foreach (var app in apps)
IdleGameList.Add(app);
return true;
}
catch (Exception ex)
@@ -445,11 +428,10 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
{
var multi = IdleGameList.Where(z => z.Badge.HoursPlayed >= SteamIdleSettings.MinRunTime.Value).ToList();
var isLastSingle = multi.Count == 1 && IdleGameList.Count == 1; // 是否只剩最后一个游戏
if (multi.Count > 1 || isLastSingle)
if (multi.Count > 1 || (multi.Count == 1 && IdleGameList.Count == 1))
{
idleApp = VerifyIsNext(multi, isNext);
PauseAutoNext(isLastSingle);
PauseAutoNext(false);
StartSoloIdle(idleApp);
}
else
@@ -795,23 +777,6 @@ public sealed partial class IdleCardPageViewModel : ViewModelBase
Toast.Show(ToastIcon.Success, message);
INotificationService.Instance.Notify(message, NotificationType.Message);
}
private async Task RefreshAccessTokenAsync(string new_accessToken)
{
SteamLoginState.AccessToken = new_accessToken;
SteamSession session = new SteamSession()
{
SteamId = SteamLoginState.SteamId.ToString(),
AccessToken = SteamLoginState.AccessToken,
RefreshToken = SteamLoginState.RefreshToken
};
session.GenerateSetCookie();
var sessionService = Ioc.Get<ISteamSessionService>();
sessionService.AddOrSetSeesion(session);
if (LoginViewModel is null || LoginViewModel.RemenberLogin)
await sessionService.SaveSession(session);
}
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -0,0 +1,17 @@
namespace BD;
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class MobiusAttribute : Attribute
{
public MobiusAttribute()
{
}
public MobiusAttribute(string str)
{
}
public bool Obsolete { get; set; }
}
@@ -122,16 +122,11 @@
<PackageReference Include="Squid-Box.SevenZipSharp.Lite" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="ZstdNet" />
<!--<PackageReference Include="BsDiff" />-->
<PackageReference Include="7-Zip.NativeAssets.Win32" />
<PackageReference Include="Portable.BouncyCastle" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ref\WTTS.MicroServices.ClientSDK\src\BD.WTTS.MicroServices.ClientSDK\BD.WTTS.MicroServices.ClientSDK.csproj" />
</ItemGroup>
<Import Project="..\TFM_NETX.props" />
</Project>
@@ -1,492 +0,0 @@
//using BsDiff;
//using JsonSerializer = System.Text.Json.JsonSerializer;
//using BD.WTTS.Models;
//namespace BD.WTTS.Client.Tools.Publish.Commands;
//interface IGenerateBsPatch : ICommand
//{
// const string commandName = "bs";
// const string BaseUrl =
//#if DEBUG
// "https://localhost:9881";
//#else
// "http://192.168.2.16:19001";
//#endif
// static Command ICommand.GetCommand()
// {
// var type = new Option<int>("--type", "type 0:Create 1:Apply 2:UploadFile 3:传入版本号生成补丁");
// //旧文件
// var oldFile = new Option<string>("--old", "old file");
// //新文件
// var newFile = new Option<string>("--new", "new file");
// //保存路径
// var patchFile = new Option<string>("--path", "new file");
// //程序版本号
// var clientPlatform = new Option<ClientPlatform>("--platform", "ClientPlatform");
// var command = new Command(commandName, "Bridge csproj xml generate")
// {
// type, oldFile, newFile, patchFile, clientPlatform
// };
// command.SetHandler(Handler, type, oldFile, newFile, patchFile, clientPlatform);
// return command;
// }
// internal static void Handler(int type, string oldFile, string newFile, string patchFile, ClientPlatform platform = ClientPlatform.Win32X64)
// {
// if (type < 2)
// {
// if (!File.Exists(oldFile))
// {
// Console.WriteLine("old file not exists");
// return;
// }
// if (!File.Exists(newFile))
// {
// Console.WriteLine("new file not exists");
// return;
// }
// if (string.IsNullOrWhiteSpace(patchFile))
// {
// Console.WriteLine("The patch file path cannot be empty");
// return;
// }
// }
// switch (type)
// {
// case 0:
// using (var output = new FileStream(patchFile, FileMode.Create))
// GenerateBsPatch(File.ReadAllBytes(oldFile), File.ReadAllBytes(newFile), output);
// break;
// case 1:
// using (var input = new FileStream(oldFile, FileMode.Open, FileAccess.Read, FileShare.Read))
// using (var output = new FileStream(newFile, FileMode.Create))
// BinaryPatch.Apply(input, () => new FileStream(patchFile, FileMode.Open, FileAccess.Read, FileShare.Read), output);
// break;
// case 2:
// UploadFiles(oldFile).Wait();
// break;
// case 3:
// GenerateAppPublishPatchBundleInfo(oldFile, newFile, platform, patchFile).Wait();
// break;
// case 4:
// UploadAppVer(oldFile, newFile, platform).Wait();
// break;
// case 5:
// ApplyPatch(oldFile, newFile, patchFile).Wait();
// break;
// }
// Console.WriteLine("end");
// }
// /// <summary>
// /// 应用补丁
// /// </summary>
// /// <param name="applyPath"></param>
// /// <param name="saveFilePath"></param>
// /// <param name="patchPath"></param>
// /// <returns></returns>
// internal static async ThreadTask ApplyPatch(string applyPath, string saveFilePath, string patchPath)
// {
// if (!File.Exists(patchPath))
// {
// Console.WriteLine("PatchPath not exists");
// return;
// }
// if (!Directory.Exists(applyPath))
// {
// Console.WriteLine("ApplyPath not exists");
// return;
// }
// var fileStream = File.OpenRead(patchPath);
// var jsonData = new BrotliStream(fileStream, CompressionMode.Decompress);
// var dic = await MemoryPackSerializer.DeserializeAsync<Dictionary<string, PatchBundleInfo?>>(jsonData);
// if (dic is null)
// {
// Console.WriteLine("Patch Error");
// return;
// }
// if (!Directory.Exists(saveFilePath))
// {
// Directory.CreateDirectory(saveFilePath);
// }
// else
// {
// Directory.Delete(saveFilePath);
// }
// CopyDirectory(applyPath, saveFilePath);
// FileInfo? fileInfo = null;
// foreach (var item in dic.Where(x => x.Value is not null))
// {
// PatchBundleInfo info = item.Value!;
// if (info.PatchData is not null)
// {
// fileInfo = new FileInfo(Path.Combine(saveFilePath, item.Key));
// if (!(fileInfo.Directory?.Exists ?? false))
// fileInfo.Directory?.Create();
// if (info.IsFullOrPatch)
// {
// File.WriteAllBytes(Path.Combine(saveFilePath, item.Key), info.PatchData);
// }
// else
// {
// using var oldFile = File.OpenRead(Path.Combine(applyPath, item.Key));
// var newFile = Path.Combine(saveFilePath, item.Key);
// using var saveFile = File.Exists(newFile) ? File.Create(newFile) : File.OpenWrite(newFile);
// BinaryPatch.Apply(oldFile, () => new MemoryStream(info.PatchData), saveFile);
// }
// }
// }
// Console.WriteLine("ApplyPatch OK");
// }
// /// <summary>
// /// 复制目录下的文件
// /// </summary>
// /// <param name="sourceDir"></param>
// /// <param name="targetDir"></param>
// /// <exception cref="DirectoryNotFoundException"></exception>
// public static void CopyDirectory(string sourceDir, string targetDir)
// {
// DirectoryInfo dir = new DirectoryInfo(sourceDir);
// DirectoryInfo[] dirs = dir.GetDirectories();
// // If the source directory does not exist, throw an exception.
// if (!dir.Exists)
// {
// throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDir}");
// }
// // If the destination directory does not exist, create it.
// if (!Directory.Exists(targetDir))
// {
// Directory.CreateDirectory(targetDir);
// }
// // Get the files in the directory and copy them to the new location.
// FileInfo[] files = dir.GetFiles();
// foreach (FileInfo file in files)
// {
// string tempPath = Path.Combine(targetDir, file.Name);
// file.CopyTo(tempPath, false);
// }
// // If copying subdirectories, copy them and their contents to the new location.
// foreach (DirectoryInfo subdir in dirs)
// {
// string tempPath = Path.Combine(targetDir, subdir.Name);
// CopyDirectory(subdir.FullName, tempPath);
// }
// }
// /// <summary>
// /// 根据 2个 文件生成补丁
// /// </summary>
// /// <param name="oldByte"></param>
// /// <param name="newByte"></param>
// /// <param name="output"></param>
// internal static void GenerateBsPatch(byte[] oldByte, byte[] newByte, Stream output)
// {
// BinaryPatch.Create(oldByte, newByte, output);
// }
// /// <summary>
// /// 上传版本信息 加 FileList
// /// </summary>
// /// <param name="appVer"></param>
// /// <param name="jsonPath">发布工具生成的 Json</param>
// /// <param name="platform"></param>
// /// <returns></returns>
// internal static async ThreadTask UploadAppVer(string appVer, string jsonPath, ClientPlatform platform)
// {
// if (string.IsNullOrWhiteSpace(appVer))
// {
// Console.WriteLine("AppVer not exists");
// return;
// }
// if (string.IsNullOrWhiteSpace(jsonPath))
// {
// Console.WriteLine("jsonPath not exists");
// return;
// }
// if (!File.Exists(jsonPath))
// {
// Console.WriteLine("jsonPath not Find");
// return;
// }
// var fileListRaw = File.ReadAllBytes(jsonPath);
// var appver = await GetAppVer(appVer);
// if (appver is not null)
// {
// var thisFileList = appver.FileList?.FirstOrDefault(x => x.Platform == platform);
// if (thisFileList is not null)
// {
// if (Hashs.String.SHA384(thisFileList.FileListRaw) != Hashs.String.SHA384(fileListRaw))
// {
// thisFileList.FileListRaw = fileListRaw;
// }
// }
// else
// {
// if (appver.FileList is null)
// {
// appver.FileList = new List<AppVerFileList>
// {
// new AppVerFileList
// {
// Platform = platform,
// FileListRaw = fileListRaw
// }
// };
// }
// else
// {
// appver.FileList.Add(new AppVerFileList
// {
// Platform = platform,
// FileListRaw = fileListRaw
// });
// }
// }
// await UploadAppVer(appver);
// }
// else
// {
// appver = new AppVer
// {
// Platform = platform,
// Ver = appVer,
// Published = DateTimeOffset.Now,
// FileList = new List<AppVerFileList>
// {
// new AppVerFileList
// {
// Platform = platform,
// FileListRaw = fileListRaw
// }
// }
// };
// await UploadAppVer(appver);
// }
// }
// /// <summary>
// /// 上传版本信息
// /// </summary>
// /// <param name="appVer"></param>
// /// <returns></returns>
// internal static async ThreadTask UploadAppVer(AppVer appVer)
// {
// using var httpClient = new HttpClient();
// var content = JsonContent.Create(appVer);
// var response = await httpClient.PostAsync($"{BaseUrl}/appver/", content);
// }
// /// <summary>
// /// 传入 2 个版本号生成补丁
// /// </summary>
// /// <param name="oldVer"></param>
// /// <param name="newVer"></param>
// /// <param name="platform"></param>
// /// <param name="patchFilePath"></param>
// /// <returns></returns>
// internal static async ThreadTask GenerateAppPublishPatchBundleInfo(string oldVer, string newVer, ClientPlatform platform, string patchFilePath)
// {
// var oldAppVer = await GetAppVer(oldVer);
// var newAppVer = await GetAppVer(newVer);
// if (oldAppVer is null || newAppVer is null)
// {
// Console.WriteLine("AppVer not exists");
// return;
// }
// var oldFileList = await GetAppPublishInfo(oldAppVer.Id, platform);
// var newFileList = await GetAppPublishInfo(newAppVer.Id, platform);
// if (oldFileList is null || newFileList is null)
// {
// Console.WriteLine("AppPublishInfo not exists");
// return;
// }
// var oldAppVerFileList = JsonSerializer.Deserialize<AppPublishInfo>(oldFileList.FileListRaw);
// var newAppVerFileList = JsonSerializer.Deserialize<AppPublishInfo>(newFileList.FileListRaw);
// if (oldAppVerFileList is null || newAppVerFileList is null)
// {
// Console.WriteLine("AppPublishInfo not exists");
// return;
// }
// var pathSavePath = Path.Combine(patchFilePath, oldVer, $"{newVer}.dat");
// var pathFileInfo = new FileInfo(pathSavePath);
// if (!(pathFileInfo.Directory?.Exists ?? false))
// {
// pathFileInfo.Directory?.Create();
// }
// Console.WriteLine($"开始生成: {oldVer} -> {newVer} platform:{platform}");
// await ContrastVersion(oldAppVerFileList, newAppVerFileList, pathSavePath);
// }
// /// <summary>
// /// 获取版本号信息
// /// </summary>
// /// <param name="ver"></param>
// /// <returns></returns>
// public static async Task<AppVer?> GetAppVer(string ver)
// {
// using var httpClient = new HttpClient();
// var response = await httpClient.GetAsync($"{BaseUrl}/appver/{ver}");
// var data = JsonSerializer.Deserialize<ApiRsp<AppVer?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions
// {
// PropertyNameCaseInsensitive = true
// });
// return data?.Content;
// }
// /// <summary>
// /// 更具 APP 版本 ID 获取发布信息
// /// </summary>
// /// <param name="appVerId">版本号</param>
// /// <param name="platform">平台</param>
// /// <returns></returns>
// public static async Task<AppVerFileList?> GetAppPublishInfo(Guid appVerId, ClientPlatform platform)
// {
// using var httpClient = new HttpClient();
// var response = await httpClient.GetAsync($"{BaseUrl}/appver/file/{appVerId}/{platform}/list");
// var data = JsonSerializer.Deserialize<ApiRsp<AppVerFileList?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions
// {
// PropertyNameCaseInsensitive = true
// });
// return data?.Content;
// }
// /// <summary>
// /// 根据 2 个版本的文件列表生成补丁
// /// </summary>
// /// <param name="oldFileList"></param>
// /// <param name="newFileList"></param>
// /// <param name="patchFilePath"></param>
// /// <returns></returns>
// public static async ThreadTask ContrastVersion(AppPublishInfo oldFileList, AppPublishInfo newFileList, string patchFilePath)
// {
// var oldFiles = oldFileList.Files.Where(x => !x.RelativePath.StartsWith("dotnet\\"));
// var newFiles = newFileList.Files.Where(x => !x.RelativePath.StartsWith("dotnet\\"));
// Console.WriteLine($"旧版本文件数:{oldFiles.Count()}");
// Console.WriteLine($"新版本文件数:{newFiles.Count()}");
// var changeList = new Dictionary<string, PatchBundleInfo?>();
// var removeFiles = oldFiles.Select(x => x.RelativePath).Except(newFiles.Select(x => x.RelativePath));
// //文件删除
// foreach (var item in removeFiles)
// {
// changeList.Add(item, null);
// }
// Console.WriteLine($"需要删除的文件数:{changeList.Count}");
// foreach (var item in newFiles)
// {
// var oldFile = oldFiles.FirstOrDefault(x => x.RelativePath.Equals(item.RelativePath));
// //文件新增
// if (oldFile == null)
// {
// var newFileRaw = string.IsNullOrWhiteSpace(item.SignatureSHA384) ? await GetFileRaw(item.SHA384) : await GetFileRaw(item.SignatureSHA384);
// if (newFileRaw is null)
// {
// Console.WriteLine("new file not exists");
// return;
// }
// changeList.Add(item.RelativePath, new PatchBundleInfo
// {
// SHA384 = item.SHA384,
// IsFullOrPatch = true,
// PatchData = newFileRaw.DataRaw
// });
// }
// else
// {
// //文件修改
// if (oldFile.SHA384 != item.SHA384)
// {
// var oldFileRaw = string.IsNullOrWhiteSpace(oldFile.SignatureSHA384) ? await GetFileRaw(oldFile.SHA384) : await GetFileRaw(oldFile.SignatureSHA384);
// if (oldFileRaw is null)
// {
// Console.WriteLine("old file not exists");
// return;
// }
// var newFileRaw = string.IsNullOrWhiteSpace(item.SignatureSHA384) ? await GetFileRaw(item.SHA384) : await GetFileRaw(item.SignatureSHA384); // await GetFileRaw(item.SignatureSHA384);
// if (newFileRaw is null)
// {
// Console.WriteLine("new file not exists");
// return;
// }
// using (var output = new MemoryStream())
// {
// //BinaryPatch.Create(oldFileRaw.DataRaw, File.ReadAllBytes(item.RelativePath), output);
// BinaryPatch.Create(oldFileRaw.DataRaw, newFileRaw.DataRaw, output);
// changeList.Add(item.RelativePath, new PatchBundleInfo
// {
// SHA384 = item.SHA384,
// IsFullOrPatch = false,
// PatchData = output.ToArray()
// });
// }
// }
// }
// }
// if (File.Exists(patchFilePath))
// File.Delete(patchFilePath);
// Console.WriteLine($"变动文件数量:{changeList.Count}");
// using var fileStream = File.OpenWrite(patchFilePath);
// using var brSave = new BrotliStream(fileStream, CompressionLevel.SmallestSize);
// Console.WriteLine($"开始压缩");
// await MemoryPackSerializer.SerializeAsync(typeof(Dictionary<string, PatchBundleInfo?>), brSave, changeList);
// //fileStream.Flush();
// Console.WriteLine($"结束");
// }
// /// <summary>
// /// Hash 384 获取源文件数据
// /// </summary>
// /// <param name="sha384"></param>
// /// <returns></returns>
// public static async Task<AppVerFile?> GetFileRaw(string sha384)
// {
// using var httpClient = new HttpClient();
// var response = await httpClient.GetAsync($"{BaseUrl}/appver/file/{sha384}");
// var data = JsonSerializer.Deserialize<ApiRsp<AppVerFile?>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
// return data?.Content;
// }
// /// <summary>
// /// 上传指定文件到服务器
// /// </summary>
// /// <param name="directoryPath">需要上传的目录</param>
// internal static async ThreadTask UploadFiles(string directoryPath)
// {
// DirectoryInfo directoryInfo = new(directoryPath);
// if (directoryInfo.Exists)
// {
// using var httpClient = new HttpClient();
// var fileList = new List<AppPublishFileInfo>();
// IScanPublicDirectoryCommand.ScanPathCore(directoryPath, fileList, null, null);
// using var form = new MultipartFormDataContent();
// fileList.ForEach(file =>
// {
// var fileStream = file.FileInfo!.OpenRead();
// form.Add(new StreamContent(fileStream), file.FileInfo!.Name, file.FileInfo!.Name);
// });
// if (form.Count() > 0)
// {
// var response = await httpClient.PostAsync($"{BaseUrl}/appver/file/upload", form);
// Console.WriteLine(response);
// }
// }
// else
// {
// Console.WriteLine("文件夹不存在");
// }
// }
//}
@@ -0,0 +1,17 @@
namespace BD;
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public sealed class MobiusAttribute : Attribute
{
public MobiusAttribute()
{
}
public MobiusAttribute(string str)
{
}
public bool Obsolete { get; set; }
}
@@ -1,47 +0,0 @@
namespace BD.WTTS.Client.Tools.Publish.Models;
/// <summary>
/// 程序发布【修补包】信息
/// </summary>
[MP2Obj(SerializeLayout.Explicit)]
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public sealed partial class AppPublishPatchBundleInfo
{
string DebuggerDisplay() => $"{OldAppVer}-{NewAppVer} {SHA384} {IOPath.GetDisplayFileSizeString(Length)}";
/// <summary>
/// 当前包适用程序版本
/// </summary>
[MP2Key(0)]
public string OldAppVer { get; set; } = string.Empty;
/// <summary>
/// 当前包适用程序版本
/// </summary>
[MP2Key(1)]
public string NewAppVer { get; set; } = string.Empty;
/// <summary>
/// 修补包 SHA384 值
/// </summary>
[MP2Key(2)]
public string SHA384 { get; set; } = string.Empty;
/// <summary>
/// 修补包文件大小
/// </summary>
[MP2Key(3)]
public long Length { get; set; }
/// <summary>
/// 修补包文件路径
/// </summary>
[MP2Key(4)]
public string FilePath { get; set; } = string.Empty;
/// <summary>
/// 修补包数据 (文件相对路径,包信息 为 null 删除文件) 不存在其中的文件全量复制
/// </summary>
[MP2Key(5)]
public Dictionary<string, PatchBundleInfo?> PatchData { get; set; } = new();
}
@@ -1,23 +0,0 @@
namespace BD.WTTS.Client.Tools.Publish.Models;
public sealed class AppVer
{
public Guid Id { get; set; }
/// <summary>
/// 版本号
/// </summary>
public string Ver { get; set; } = "";
/// <summary>
/// 发布时间
/// </summary>
public DateTimeOffset Published { get; set; }
/// <summary>
/// 支持的平台
/// </summary>
public ClientPlatform Platform { get; set; }
public IList<AppVerFileList>? FileList { get; set; }
}
@@ -1,26 +0,0 @@
namespace BD.WTTS.Client.Tools.Publish.Models;
public sealed class AppVerFile
{
public Guid Id { get; set; }
/// <summary>
/// 文件 Hash
/// </summary>
public string SHA384 { get; set; } = "";
/// <summary>
/// 发布时间
/// </summary>
public DateTimeOffset Published { get; set; }
/// <summary>
/// 文件原始 Byte 数据
/// </summary>
public byte[] DataRaw { get; set; } = Array.Empty<byte>();
/// <summary>
/// 文件大小
/// </summary>
public long Length { get; set; }
}
@@ -1,23 +0,0 @@
namespace BD.WTTS.Client.Tools.Publish.Models;
public sealed class AppVerFileList
{
public Guid Id { get; set; }
/// <summary>
/// 版本号ID
/// </summary>
public Guid AppVerId { get; set; }
/// <summary>
/// 平台
/// </summary>
public ClientPlatform Platform { get; set; }
/// <summary>
/// 文件清单列表
/// </summary>
public byte[] FileListRaw { get; set; } = Array.Empty<byte>();
public AppVer? AppVerInfo { get; set; }
}
@@ -1,27 +0,0 @@
namespace BD.WTTS.Client.Tools.Publish.Models;
/// <summary>
/// 修补包信息
/// </summary>
[MP2Obj(SerializeLayout.Explicit)]
public sealed partial class PatchBundleInfo
{
/// <summary>
/// 文件修补完后SHA384
/// </summary>
[MP2Key(0)]
public string? SHA384 { get; set; }
/// <summary>
/// 补丁包数据
/// </summary>
[MP2Key(1)]
public byte[]? PatchData { get; set; }
/// <summary>
/// 是否全量或补丁
/// </summary>
[MP2Key(2)]
public bool IsFullOrPatch { get; set; }
}
@@ -6,91 +6,7 @@
"commandLineArgs_release": "run --rids win-x64 --hsm-sign --stm-upload",
"commandLineArgs_nsis": "nsis --rids win-x64 --hsm-sign --t 240314_1523131679333",
"commandLineArgs_debug": "run --rids win-x64 --hsm-sign --stm-upload --debug",
"commandLineArgs_GenerateBridgeContentXml": "csproj --rids win-x64",
"commandLineArgs_TestUploadFile": "bs --type 2 --old \"C:\\Users\\Mossimo\\Desktop\\Steam++\\ms-test.steampp.net\""
},
"BD.WTTS.Client.Tools.Publish(TestUploadRc1)": {
"commandName": "Project",
"commandLineArgs": "bs --type 2 --old \"D:\\BuildTest\\rc1\""
},
"BD.WTTS.Client.Tools.Publish(TestUploadRc2)": {
"commandName": "Project",
"commandLineArgs": "bs --type 2 --old \"D:\\BuildTest\\rc2\""
},
"BD.WTTS.Client.Tools.Publish(TestUploadRc4)": {
"commandName": "Project",
"commandLineArgs": "bs --type 2 --old \"D:\\BuildTest\\rc4\""
},
"BD.WTTS.Client.Tools.Publish(TestUploadRc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 2 --old \"D:\\BuildTest\\rc5\""
},
"BD.WTTS.Client.Tools.Publish(AppVer.Rc1)": {
"commandName": "Project",
"commandLineArgs": "bs --type 4 --old 3.0.107.0 --new \"D:\\BuildTest\\rc1.json\" --platform 2"
},
"BD.WTTS.Client.Tools.Publish(AppVer.Rc2)": {
"commandName": "Project",
"commandLineArgs": "bs --type 4 --old 3.0.207.0 --new \"D:\\BuildTest\\rc2.json\" --platform 2"
},
"BD.WTTS.Client.Tools.Publish(AppVer.Rc4)": {
"commandName": "Project",
"commandLineArgs": "bs --type 4 --old 3.0.407.0 --new \"D:\\BuildTest\\rc4.json\" --platform 2"
},
"BD.WTTS.Client.Tools.Publish(AppVer.Rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 4 --old 3.0.507.0 --new \"D:\\BuildTest\\rc5.json\" --platform 2"
},
"GenerateAppPublishPatchBundleInfo(rc1-rc2)": {
"commandName": "Project",
"commandLineArgs": "bs --type 3 --old 3.0.107.0 --new 3.0.207.0 --platform 2 --path \"D:\\BuildTest\""
},
"GenerateAppPublishPatchBundleInfo(rc1-rc4)": {
"commandName": "Project",
"commandLineArgs": "bs --type 3 --old 3.0.107.0 --new 3.0.407.0 --platform 2 --path \"D:\\BuildTest\""
},
"GenerateAppPublishPatchBundleInfo(rc1-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 3 --old 3.0.107.0 --new 3.0.507.0 --platform 2 --path \"D:\\BuildTest\""
},
"GenerateAppPublishPatchBundleInfo(rc2-rc4)": {
"commandName": "Project",
"commandLineArgs": "bs --type 3 --old 3.0.207.0 --new 3.0.407.0 --platform 2 --path \"D:\\BuildTest\""
},
"GenerateAppPublishPatchBundleInfo(rc2-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 3 --old 3.0.207.0 --new 3.0.507.0 --platform 2 --path \"D:\\BuildTest\""
},
"GenerateAppPublishPatchBundleInfo(rc4-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 3 --old 3.0.407.0 --new 3.0.507.0 --platform 2 --path \"D:\\BuildTest\""
},
"ApplyPatch(rc1-rc2)": {
"commandName": "Project",
"commandLineArgs": "bs --type 5 --old \"D:\\BuildTest\\rc1\" --new \"D:\\BuildTest\\pacth\\rc1-rc2\" --path \"D:\\BuildTest\\3.0.107.0\\3.0.207.0.dat\""
},
"ApplyPatch(rc1-rc2-rc4)": {
"commandName": "Project",
"commandLineArgs": "bs --type 5 --old \"D:\\BuildTest\\pacth\\rc1-rc2\" --new \"D:\\BuildTest\\pacth\\rc1-rc2-rc4\" --path \"D:\\BuildTest\\3.0.207.0\\3.0.407.0.dat\""
},
"ApplyPatch(rc1-rc2-rc4-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 5 --old \"D:\\BuildTest\\pacth\\rc1-rc2-rc4\" --new \"D:\\BuildTest\\pacth\\rc1-rc2-rc4-rc5\" --path \"D:\\BuildTest\\3.0.407.0\\3.0.507.0.dat\""
},
"ApplyPatch(rc1-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 5 --old \"D:\\BuildTest\\rc1\" --new \"D:\\BuildTest\\pacth\\rc1-rc5\" --path \"D:\\BuildTest\\3.0.107.0\\3.0.507.0.dat\""
},
"ApplyPatch(rc2-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 5 --old \"D:\\BuildTest\\rc2\" --new \"D:\\BuildTest\\pacth\\rc2-rc5\" --path \"D:\\BuildTest\\3.0.207.0\\3.0.507.0.dat\""
},
"ApplyPatch(rc4-rc5)": {
"commandName": "Project",
"commandLineArgs": "bs --type 5 --old \"D:\\BuildTest\\rc4\" --new \"D:\\BuildTest\\pacth\\rc4-rc5\" --path \"D:\\BuildTest\\3.0.407.0\\3.0.507.0.dat\""
"commandLineArgs_GenerateBridgeContentXml": "csproj --rids win-x64"
}
}
}
@@ -10,6 +10,8 @@ partial interface IApplication
/// </summary>
/// <param name="iconKey"></param>
/// <returns></returns>
[Mobius(Obsolete = true)]
[Obsolete]
string? GetIconSourceByIconKey(string? iconKey) => string.IsNullOrWhiteSpace(iconKey) ? null :
#if WINDOWS || MACCATALYST || MACOS || LINUX
$"avares://BD.WTTS.Client.Avalonia.App/UI/Assets/Icons/{iconKey}.ico";
@@ -22,6 +24,8 @@ partial interface IApplication
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
[Mobius(Obsolete = true)]
[Obsolete]
string GetIconKeyByTypeName(string typeName)
{
#if WINDOWS || MACCATALYST || MACOS || LINUX
@@ -12,6 +12,12 @@ partial interface IApplication
/// <param name="msgToast"></param>
/// <param name="showToast"></param>
/// <returns></returns>
[Mobius(
"""
App // ICommand
async void CopyToClipboardCommandCore(object? text)
ICommand CopyToClipboardCommand
""")]
static async Task CopyToClipboardAsync(string? text, string? msgToast = null, bool showToast = true)
{
if (!string.IsNullOrWhiteSpace(text))
@@ -3,37 +3,65 @@ namespace BD.WTTS;
partial interface IApplication : IDisposableHolder
{
[Mobius(
"""
App.CompositeDisposable
""")]
new CompositeDisposable CompositeDisposable { get; }
/// <summary>
/// 切换当前桌面应用的主题而不改变设置值
/// </summary>
/// <param name="value"></param>
[Mobius(
"""
App.SetThemeNotChangeValue
""")]
void SetThemeNotChangeValue(AppTheme value) { }
/// <summary>
/// 退出整个程序
/// </summary>
[Mobius(
"""
App.Shutdown
""")]
void Shutdown() { }
/// <summary>
/// 主窗口恢复显示
/// </summary>
[Mobius(
"""
App.RestoreMainWindow
""")]
void RestoreMainWindow() { }
/// <summary>
/// 主窗口置顶一次
/// </summary>
[Mobius(
"""
App.RestoreMainWindow
""")]
void SetTopmostOneTime() { }
/// <summary>
/// 托盘菜单
/// </summary>
[Mobius(
"""
App.TrayIconMenus
""")]
IReadOnlyDictionary<string, ICommand> TrayIconMenus { get; }
/// <summary>
/// 是否有活动窗口
/// </summary>
/// <returns></returns>
[Mobius(
"""
App.HasActiveWindow
""")]
bool HasActiveWindow();
}
@@ -5,8 +5,16 @@ partial interface IApplication
{
#region Logger
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
const LogLevel DefaultLoggerMinLevel = IPCSubProcessFileSystem.DefaultLoggerMinLevel;
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
public static NLogLevel DefaultNLoggerMinLevel => IPCSubProcessFileSystem.DefaultNLoggerMinLevel;
/// <summary>
@@ -14,12 +22,24 @@ partial interface IApplication
/// </summary>
/// <param name="logLevel">level to be converted.</param>
/// <returns></returns>
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static NLogLevel ConvertLogLevel(LogLevel logLevel) => IPCSubProcessFileSystem.ConvertLogLevel(logLevel);
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void SetNLoggerMinLevel(LogLevel logLevel) => SetNLoggerMinLevel(ConvertLogLevel(logLevel));
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void SetNLoggerMinLevel(NLogLevel logLevel)
{
@@ -27,6 +47,7 @@ partial interface IApplication
NInternalLogger.LogLevel = logLevel;
}
[Mobius(Obsolete = true)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TrySetLoggerMinLevel(LogLevel logLevel)
{
@@ -41,11 +62,19 @@ partial interface IApplication
SetNLoggerMinLevel(ConvertLogLevel(logLevel));
}
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
private static LoggerFilterOptions? _LoggerFilterOptions;
/// <summary>
/// 日志过滤选项
/// </summary>
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
static LoggerFilterOptions? LoggerFilterOptions
{
get
@@ -59,6 +88,10 @@ partial interface IApplication
/// <summary>
/// 配置 NLog 提供程序,仅初始化时调用
/// </summary>
[Mobius(
"""
Mobius.Helpers.LogInitHelper.GetMobiusLoggingBuilder
""")]
public static Action<ILoggingBuilder> ConfigureLogging(LogLevel minLevel = DefaultLoggerMinLevel)
{
return (ILoggingBuilder builder) =>
@@ -92,6 +125,10 @@ partial interface IApplication
/// <summary>
/// 日志最低等级
/// </summary>
[Mobius(
"""
Mobius.Helpers.LogInitHelper
""")]
public static LogLevel LoggerMinLevel
{
get
@@ -119,6 +156,7 @@ partial interface IApplication
/// <summary>
/// 启用日志
/// </summary>
[Mobius(Obsolete = true)]
public static bool EnableLogger
{
get => LoggerMinLevel > LogLevel.None;
@@ -135,6 +173,10 @@ partial interface IApplication
/// <summary>
/// 日志存放文件夹路径
/// </summary>
[Mobius(
"""
MobiusHost.LogDirPath
""")]
static string LogDirPath { get; internal set; } = string.Empty;
#if (WINDOWS || MACCATALYST || MACOS || LINUX) && !(IOS || ANDROID)
@@ -142,6 +184,7 @@ partial interface IApplication
/// <summary>
/// 日志存放文件夹路径(ASF)
/// </summary>
[Mobius(Obsolete = true)]
static string LogDirPathASF { get; set; } = string.Empty;
#endif
@@ -154,6 +197,7 @@ partial interface IApplication
static bool LogUnderCache => true;
#endif
[Mobius(Obsolete = true)]
static Action? ASFInitCoreLoggers { get; private set; }
#endregion
@@ -3,6 +3,10 @@ namespace BD.WTTS;
partial interface IApplication
{
[Mobius(
"""
MobiusHost.EnableDevTools
""")]
public static bool EnableDevtools { get; set; } =
#if DEBUG
true
@@ -14,10 +18,18 @@ partial interface IApplication
/// <summary>
/// 禁用 GPU 硬件加速
/// </summary>
[Mobius(
"""
MobiusHost.DisableGPU
""")]
public static bool DisableGPU { get; set; }
/// <summary>
/// 使用 native OpenGL(仅 Windows)
/// </summary>
[Mobius(
"""
MobiusHost.UseWgl
""")]
public static bool UseWgl { get; set; }
}
@@ -20,11 +20,19 @@ partial interface IApplication
}
#endif
[Mobius(
"""
AppConstants.ProgramName
""")]
private static string? _ProgramName;
/// <summary>
/// 获取当前主程序文件名,例如 steam++.exe
/// </summary>
[Mobius(
"""
AppConstants.ProgramName
""")]
static string ProgramName
{
get
@@ -34,6 +42,10 @@ partial interface IApplication
}
}
[Mobius(
"""
AppConstants.ProgramName
""")]
private static string GetProgramName()
{
var processPath = Environment.ProcessPath;
@@ -6,11 +6,19 @@ partial interface IApplication
/// <summary>
/// 获取或设置当前应用的主题,例如跟随系统,浅色,深色
/// </summary>
[Mobius(
"""
App.Theme
""")]
AppTheme Theme { get; set; }
/// <summary>
/// 获取当前应用的实际主题
/// </summary>
[Mobius(
"""
App.ActualTheme
""")]
AppTheme ActualTheme => Theme switch
{
AppTheme.FollowingSystem => GetActualThemeByFollowingSystem(),
@@ -22,8 +30,13 @@ partial interface IApplication
/// <summary>
/// 获取当前应用的默认主题
/// </summary>
[Mobius(
"""
App.DefaultActualTheme
""")]
protected AppTheme DefaultActualTheme { get; }
[Mobius(Obsolete = true)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static AppTheme GetAppThemeByIsLightOrDarkTheme(bool isLightOrDarkTheme) => isLightOrDarkTheme ? AppTheme.Light : AppTheme.Dark;
@@ -31,6 +44,10 @@ partial interface IApplication
/// 获取当前应用主题跟随系统时的实际主题
/// </summary>
/// <returns></returns>
[Mobius(
"""
App.GetActualThemeByFollowingSystem
""")]
protected AppTheme GetActualThemeByFollowingSystem()
{
var dps = IPlatformService.Instance;
@@ -2,6 +2,10 @@ namespace BD.WTTS;
public partial interface IApplication
{
[Mobius(
"""
App.TrayMenus
""")]
Dictionary<string, TrayMenuItem>? TrayMenus { get; }
/// <summary>
@@ -9,5 +13,9 @@ public partial interface IApplication
/// </summary>
/// <param name="menuKey"></param>
/// <param name="trayMenuItem"></param>
[Mobius(
"""
App.UpdateMenuItems
""")]
void UpdateMenuItems(string menuKey, TrayMenuItem trayMenuItem);
}
+34
View File
@@ -6,6 +6,12 @@ namespace BD.WTTS;
/// </summary>
public partial interface IApplication
{
[Mobius(
"""
App // ICommand
async void CopyToClipboardCommandCore(object? text)
ICommand CopyToClipboardCommand
""")]
static IApplication Instance => Ioc.Get<IApplication>();
/// <summary>
@@ -13,6 +19,7 @@ public partial interface IApplication
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Mobius(Obsolete = true)]
static bool IsDesktop()
#if WINDOWS || MACOS || LINUX
=> true;
@@ -24,6 +31,7 @@ public partial interface IApplication
=> OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || (OperatingSystem.IsLinux() && !OperatingSystem.IsAndroid());
#endif
[Mobius(Obsolete = true)]
[Obsolete("use IsDesktop", true)]
static bool IsDesktopPlatform => IsDesktop();
@@ -31,6 +39,7 @@ public partial interface IApplication
/// 获取当前平台 UI Host
/// <para>reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .Net Framework).</para>
/// </summary>
[Mobius(Obsolete = true)]
object CurrentPlatformUIHost { get; }
#if WINDOWS
@@ -52,6 +61,7 @@ public partial interface IApplication
});
#endif
[Mobius(Obsolete = true)]
DeploymentMode DeploymentMode =>
#if WINDOWS
_DeploymentMode.Value;
@@ -60,8 +70,16 @@ public partial interface IApplication
#endif
/// <inheritdoc cref="IPlatformService.SetBootAutoStart(bool, string)"/>
[Mobius(
"""
SystemBootHelper.SetBootAutoStart
""")]
static void SetBootAutoStart(bool isAutoStart) => IPlatformService.Instance.SetBootAutoStart(isAutoStart, Constants.HARDCODED_APP_NAME);
[Mobius(
"""
ISppWebApiService.clientPlatform
""")]
private static readonly Lazy<ClientPlatform> clientPlatform = new(() =>
{
var platform = DeviceInfo2.Platform();
@@ -145,13 +163,29 @@ public partial interface IApplication
return default;
});
[Mobius(
"""
App.GetApplicationIcon
""")]
static byte[] Login_512 => Properties.Resources.AppLogo_512;
[Mobius(
"""
ISppWebApiService.clientPlatform
""")]
static ClientPlatform ClientPlatform => clientPlatform.Value;
[Mobius(Obsolete = true)]
System.Drawing.Size? GetScreenSize() => null;
[Mobius(Obsolete = true)]
bool? IsAnyWindowNotMinimized() => null;
[Mobius(
"""
App // ICommand
async void OpenBrowserCommandCore(object? url)
ICommand OpenBrowserCommand
""")]
async void OpenBrowserCommandCore(object? url) => await Browser2.OpenAsync(url?.ToString());
}
@@ -5,6 +5,8 @@ namespace BD.WTTS;
/// 指定应用此特性的类需要指定的授权
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
[Mobius(Obsolete = true)]
[Obsolete]
public sealed class AuthorizeAttribute : Attribute
{
public static bool HasAuthorize(object obj)
-1
View File
@@ -110,7 +110,6 @@
<PackageReference Include="SkiaSharp" />
<PackageReference Include="Net.Codecrete.QrCodeGenerator" />
<PackageReference Include="Fleck" />
<PackageReference Include="Stun.Net" />
<PackageReference Include="System.Linq.Async" />
<!--<PackageReference Include="Portable.BouncyCastle" />-->
<PackageReference Include="ReactiveUI.Fody" />

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