mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-20 08:18:48 +08:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4573a9b494 | |||
| 8f29d7b8ea | |||
| 7156d90967 | |||
| ec726e8c48 | |||
| 488403a86b | |||
| 59474d68cd | |||
| 5ab308bb9b | |||
| bf3d6ad778 | |||
| 59356f210e | |||
| 42df02f718 | |||
| 8728126421 | |||
| e0c36a9545 | |||
| 9a2bd1a6cc | |||
| 499edb402c | |||
| 14216ab98f | |||
| 95c10dacd4 | |||
| 2883405ace | |||
| 75e5b6e897 | |||
| 66f3aa22d6 | |||
| 7930120e0b | |||
| aa44b137c4 | |||
| 103918060a | |||
| 0644bbf924 | |||
| 3853181d8a | |||
| de694024bb | |||
| 6ceaeb0710 | |||
| 71f104b947 | |||
| a9e913434a | |||
| f1ffae11aa | |||
| 7f7fde20bd | |||
| 2e97c2edc2 | |||
| 69b8590881 | |||
| 7b9ba5e8a0 | |||
| d6ab3c7f03 | |||
| 12d062d4de | |||
| f27d3c9111 | |||
| 6ab74d449e | |||
| ceed57c0f2 | |||
| 8ca203f34b | |||
| d4de20aa96 | |||
| c94dec40a1 | |||
| b829114c4f | |||
| a3a32bf5fa | |||
| faa7cc5b13 | |||
| 1e506f9930 | |||
| 217449ba54 | |||
| 55b79c4d4e | |||
| a6c5fed203 | |||
| b7e31d1c9c |
@@ -3,6 +3,12 @@
|
||||
default_base_path="$HOME/WattToolkit"
|
||||
base_path="$default_base_path"
|
||||
|
||||
# 检查是否以root身份执行
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "此脚本不能以root身份执行。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 循环直到用户输入有效路径或直接回车
|
||||
while true; do
|
||||
# 使用 zenity 提示用户选择安装路径或使用默认路径
|
||||
@@ -41,7 +47,45 @@ base_url="https://api.steampp.net"
|
||||
architecture=1
|
||||
app_name="Watt Toolkit"
|
||||
PROCESS_NAMES=("$exec_name" "$app_name")
|
||||
export LC_ALL=en_US.UTF-8
|
||||
Check_LC_Code() {
|
||||
# 检查当前 LANG 环境变量
|
||||
current_lang="${LC_ALL:-$LANG}"
|
||||
echo "当前语言环境: $current_lang"
|
||||
|
||||
# 检查是否为 C 或 C.UTF-8
|
||||
if [ "$current_lang" = "C" ] || [ "$current_lang" = "C.UTF-8" ]; then
|
||||
echo "当前语言环境是 C 或 C.UTF-8,需要修改。"
|
||||
|
||||
# 列出系统支持的语言环境
|
||||
supported_locales=$(locale -a)
|
||||
if [ -z "$supported_locales" ]; then
|
||||
echo "未能获取到支持的语言环境列表。请检查系统配置。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 遍历支持的语言环境列表,找到第一个不是 C 或 C.UTF-8 的项
|
||||
first_non_c_locale=""
|
||||
for locale in $supported_locales; do
|
||||
if [ "$locale" != "C" ] && [ "$locale" != "C.UTF-8" ]; then
|
||||
first_non_c_locale="$locale"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查是否找到了非 C/C.UTF-8 的语言环境
|
||||
if [ -n "$first_non_c_locale" ]; then
|
||||
echo "修改语言环境为支持列表中的第一个非 C/C.UTF-8 项:$first_non_c_locale"
|
||||
export LC_ALL="$first_non_c_locale"
|
||||
echo "语言环境已修改为 $first_non_c_locale。"
|
||||
else
|
||||
echo "未能找到非 C/C.UTF-8 的语言环境。请检查系统配置。"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "当前语言环境不是 C 或 C.UTF-8,无需修改。"
|
||||
fi
|
||||
}
|
||||
Check_LC_Code
|
||||
Install_certutil() {
|
||||
# 判断发行版类型
|
||||
if command -v certutil &>/dev/null; then
|
||||
@@ -360,34 +404,44 @@ Kill_Process
|
||||
Decompression
|
||||
# xdg-icon-resource install "$base_path/Icons/Watt-Toolkit.png" --size 128 Watt-Toolkit
|
||||
InitDesktop() {
|
||||
# 检查XDG_DESKTOP_DIR环境变量,如果未设置则使用默认值
|
||||
XDG_DESKTOP_DIR="${XDG_DESKTOP_DIR:-$HOME/Desktop}"
|
||||
|
||||
while true; do
|
||||
# 使用 zenity 提示用户选择安装路径或使用默认路径
|
||||
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" --column="选择" --column="路径" TRUE "$HOME/.local/share/applications/" FALSE "$HOME/Desktop")
|
||||
choice=$(zenity --list --radiolist --title="请选择要添加到的位置" \
|
||||
--column="选择" --column="路径" \
|
||||
TRUE "$XDG_DESKTOP_DIR" \
|
||||
FALSE "$HOME/.local/share/applications/")
|
||||
|
||||
# 检查用户输入
|
||||
if [ "$choice" == "$HOME/.local/share/applications/" ]; then
|
||||
target_dir="$HOME/.local/share/applications/"
|
||||
break
|
||||
elif [ "$choice" == "$HOME/Desktop" ]; then
|
||||
target_dir="$HOME/Desktop/"
|
||||
elif [ "$choice" == "$XDG_DESKTOP_DIR" ]; then
|
||||
target_dir="$XDG_DESKTOP_DIR"
|
||||
break
|
||||
else
|
||||
echo "无效选项,请输入 1 或 2。"
|
||||
# 无效选项时给出提示,并继续循环
|
||||
zenity --info --text="无效选项,请重新选择。"
|
||||
fi
|
||||
done
|
||||
#添加桌面文件
|
||||
|
||||
# 添加桌面文件
|
||||
rm -rf "$target_dir/Watt Toolkit.desktop" 2>/dev/null
|
||||
echo "#!/usr/bin/env xdg-open
|
||||
cat <<EOT > "$target_dir/Watt Toolkit.desktop"
|
||||
[Desktop Entry]
|
||||
Name=Watt Toolkit
|
||||
Exec=$base_path/$exec_name.sh
|
||||
Icon=$base_path/Icons/Watt-Toolkit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false" >"$target_dir/Watt Toolkit.desktop"
|
||||
StartupNotify=false
|
||||
EOT
|
||||
chmod +x "$target_dir/Watt Toolkit.desktop"
|
||||
|
||||
}
|
||||
|
||||
|
||||
InitDesktop
|
||||
# update-desktop-database ~/.local/share/applications
|
||||
#运行程序
|
||||
|
||||
@@ -973,7 +973,7 @@
|
||||
},
|
||||
{
|
||||
"TypeName": "string",
|
||||
"PropertyName": "CustomDohAddres",
|
||||
"PropertyName": "CustomDohAddres2",
|
||||
"DefaultValue": "null",
|
||||
"DefaultValueIsConst": true,
|
||||
"Summary": "自定义 DNS over HTTPS 地址",
|
||||
|
||||
+1
-1
Submodule ref/DirectoryPackages updated: e1d09fc078...7a43b4afd3
+1
-1
Submodule ref/SteamClient updated: 3dcd4b4cda...b449d0d434
Submodule ref/WTTS.MicroServices.ClientSDK updated: 551906c771...cb3580aa1f
@@ -21,7 +21,7 @@ public static partial class AssemblyInfo
|
||||
/// <summary>
|
||||
/// RC 版本号,范围 0~9,只增不减
|
||||
/// </summary>
|
||||
const string ver_for_rc = "9";
|
||||
const string ver_for_rc = "12";
|
||||
|
||||
public const string FileVersion = $"{Version2}.{ver_for_rc}1{ver_for_preview}.0";
|
||||
|
||||
|
||||
+7
-5
@@ -44,12 +44,14 @@ sealed class AvaloniaToastServiceImpl : IToastService
|
||||
if (NotificationManager == null)
|
||||
{
|
||||
var host = AvaloniaWindowManagerImpl.GetWindowTopLevel();
|
||||
|
||||
NotificationManager = new SnackbarManager(host)
|
||||
if (host != null)
|
||||
{
|
||||
Position = NotificationPosition.BottomRight,
|
||||
MaxItems = 5,
|
||||
};
|
||||
NotificationManager = new SnackbarManager(host)
|
||||
{
|
||||
Position = NotificationPosition.BottomRight,
|
||||
MaxItems = 5,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var notificationType = GetNotificationType(icon);
|
||||
|
||||
@@ -111,6 +111,12 @@ public sealed partial class App : Application
|
||||
{
|
||||
Task2.InBackground(() => appWindow.SplashScreen?.RunTasks(CancellationToken.None));
|
||||
}
|
||||
|
||||
IPlatformService.Instance.SetSystemSessionEnding(() =>
|
||||
{
|
||||
Log.Info("System Shutdown", "Application SafeExit...");
|
||||
Shutdown();
|
||||
});
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</spp:AppItem>
|
||||
|
||||
<spp:AppItem
|
||||
Title="Wo Long: Fallen Dynasty (卧龙:苍天陨落)"
|
||||
Title="Wo Long: Fallen Dynasty (卧龙:苍天陨落)(卧龙:苍天陨落)(卧龙:苍天陨落)(卧龙:苍天陨落)"
|
||||
MaxHeight="120"
|
||||
Description="This is a description of whatver this does"
|
||||
Tags="111">
|
||||
@@ -181,7 +181,7 @@
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,5"
|
||||
Margin="0,5,12,5"
|
||||
VerticalAlignment="Top"
|
||||
Classes="CaptionTextBlockStyle"
|
||||
FontSize="12"
|
||||
|
||||
@@ -61,6 +61,10 @@ public class SnackbarManager : TemplatedControl, IManagedNotificationManager
|
||||
{
|
||||
Install(host);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("SnackbarManager", "Install Faild, host is null");
|
||||
}
|
||||
|
||||
UpdatePseudoClasses(Position);
|
||||
}
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ sealed partial class HttpReverseProxyMiddleware
|
||||
!reverseProxyConfig.Service.OnlyEnableProxyScript)
|
||||
{
|
||||
// 部分运营商将奇怪的域名解析到 127.0.0.1 再此排除这些不支持的代理域名
|
||||
var ip = await reverseProxyConfig.DnsAnalysis.AnalysisDomainIpAsync(context.Request.Host.Value, IDnsAnalysisService.DNS_Dnspods).FirstOrDefaultAsync();
|
||||
var ip = await reverseProxyConfig.DnsAnalysis.AnalysisDomainIpAsync(context.Request.Host.Value!, IDnsAnalysisService.DNS_Dnspods).FirstOrDefaultAsync();
|
||||
if (ip == null || IPAddress.IsLoopback(ip))
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
+7
-1
@@ -24,7 +24,7 @@ sealed class DnsDohAnalysisService : GeneralHttpClientFactory
|
||||
{
|
||||
}
|
||||
|
||||
public const string DefaultDohAddres = Dnspod_DohAddres;
|
||||
public const string DefaultDohAddres = Dnspod_DohAddres2;
|
||||
|
||||
static Uri GetDohAddres(string? dohAddres)
|
||||
{
|
||||
@@ -63,6 +63,12 @@ sealed class DnsDohAnalysisService : GeneralHttpClientFactory
|
||||
var client = new HttpClient(handler);
|
||||
client.BaseAddress = dohAddresUri;
|
||||
var dnsClient = new DnsHttpClient(client);
|
||||
//暂未考虑释放问题 注意!
|
||||
//foreach (var dc in dnsClients.Values)
|
||||
//{
|
||||
// dc.Dispose();
|
||||
//}
|
||||
//dnsClients.Clear();
|
||||
dnsClients.TryAdd(dohAddresUri, dnsClient);
|
||||
return dnsClient;
|
||||
}
|
||||
|
||||
@@ -46,8 +46,10 @@ static class WinDivertInitHelper
|
||||
|
||||
void GetData()
|
||||
{
|
||||
if (data != default)
|
||||
if (data == default)
|
||||
{
|
||||
data = GetWinDivertBinRes();
|
||||
}
|
||||
}
|
||||
|
||||
if (libraryPath != null && libraryDirPath != null)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using FluentAvalonia.Core;
|
||||
using System.Reactive;
|
||||
|
||||
namespace BD.WTTS.Models;
|
||||
|
||||
public class ProxyDomainGroupViewModel : ReactiveObject
|
||||
{
|
||||
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string IconUrl { get; set; } = string.Empty;
|
||||
|
||||
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
|
||||
|
||||
public ProxyDomainGroupViewModel()
|
||||
{
|
||||
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
if (EnableProxyDomainVMs == null)
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "没有可测试的项目");
|
||||
return;
|
||||
}
|
||||
|
||||
var tasks = EnableProxyDomainVMs.Select(async enableDomain =>
|
||||
{
|
||||
enableDomain.DelayMillseconds = "- ms";
|
||||
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
|
||||
enableDomain.DelayMillseconds = success switch
|
||||
{
|
||||
true when delayMs > 20000 => "Timeout",
|
||||
true => delayMs.ToString() + " ms",
|
||||
false => "error",
|
||||
};
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (EnableProxyDomainVMs.Count(x => x.DelayMillseconds == "Timeout") == EnableProxyDomainVMs.Count)
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, "当前测试项全部未通过,请检查网络链接状况,以及代理设置里的设置项是否正常。");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace BD.WTTS.Models;
|
||||
|
||||
public class ProxyDomainViewModel : ReactiveObject
|
||||
{
|
||||
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public ProxyType ProxyType { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
[Reactive]
|
||||
public string DelayMillseconds { get; set; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool RetestButtonVisible { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public IBrush DelayColor { get; } = null!;
|
||||
|
||||
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
|
||||
|
||||
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
|
||||
{
|
||||
Name = name;
|
||||
ProxyType = proxyType;
|
||||
Url = url;
|
||||
Children = children?.AsReadOnly();
|
||||
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
|
||||
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Select(d => d != string.Empty && d != "-")
|
||||
.ToPropertyEx(this, x => x.RetestButtonVisible);
|
||||
|
||||
const int DelayMiddle = 1000;
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Select(d => d switch
|
||||
{
|
||||
"Timeout" or "error" => Brushes.Red,
|
||||
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
|
||||
=> ms switch
|
||||
{
|
||||
<= DelayMiddle => Brushes.Green,
|
||||
> DelayMiddle => Brushes.Orange,
|
||||
},
|
||||
_ => Brushes.Gray,
|
||||
})
|
||||
.ToPropertyEx(this, x => x.DelayColor);
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -375,7 +375,13 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
DnsQueryAnswerRecord.DnsRecordType dnsRecordType = DnsQueryAnswerRecord.DnsRecordType.A,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using HttpClient client = new HttpClient();
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
UseCookies = false,
|
||||
UseProxy = false,
|
||||
Proxy = HttpNoProxy.Instance,
|
||||
};
|
||||
using HttpClient client = new HttpClient(handler);
|
||||
|
||||
string queryUrl = $"{dohServer}?name={testDomain}&type={dnsRecordType}";
|
||||
|
||||
|
||||
@@ -770,6 +770,11 @@ public sealed partial class GameAcceleratorService
|
||||
|
||||
public async Task ShowXunYouWindow(bool showHide)
|
||||
{
|
||||
//if (UserService.Current.User?.WattOpenId != null)
|
||||
// await Ioc.Get<IAcceleratorService>().XY_StartEx2(
|
||||
// UserService.Current.User.WattOpenId,
|
||||
// UserService.Current.User.NickName, 0, 0, 0);
|
||||
|
||||
var result = await Ioc.Get<IAcceleratorService>().XY_ShowWinodw(showHide);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ partial class ProxyService
|
||||
IReadOnlyCollection<ScriptIPCDTO>? scripts = default;
|
||||
bool isEnableScript = ProxySettings.IsEnableScript.Value;
|
||||
bool isOnlyWorkSteamBrowser = ProxySettings.IsOnlyWorkSteamBrowser.Value;
|
||||
ushort proxyPort = ProxySettings.SystemProxyPortId.Value;
|
||||
ushort proxyPort = ProxySettings.SystemProxyPortId.Value == 0 ? ProxySettings.SystemProxyPortId.Default : ProxySettings.SystemProxyPortId.Value;
|
||||
string? proxyIp = ProxySettings.SystemProxyIp.Value;
|
||||
proxyMode = defaultProxyMode;
|
||||
bool isProxyGOG = ProxySettings.IsProxyGOG.Value;
|
||||
@@ -64,7 +64,7 @@ partial class ProxyService
|
||||
string? proxyDNS = ProxySettings.ProxyMasterDns.Value;
|
||||
bool isSupportIpv6 = await RefreshIpv6Support();
|
||||
bool useDoh = ProxySettings.UseDoh.Value;
|
||||
string? customDohAddres = ProxySettings.CustomDohAddres.Value;
|
||||
string? customDohAddres = ProxySettings.CustomDohAddres2.Value;
|
||||
|
||||
Lazy<IPAddress> proxyIp_ = new(() => ReverseProxySettings.GetProxyIp(proxyIp));
|
||||
void SetProxyIp(IPAddress proxyIPAddress)
|
||||
|
||||
@@ -153,7 +153,7 @@ public partial interface IProxySettings
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址
|
||||
/// </summary>
|
||||
string? CustomDohAddres { get; set; }
|
||||
string? CustomDohAddres2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加速页面 Tab 选项卡选中下标
|
||||
@@ -278,7 +278,7 @@ public partial interface IProxySettings
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址的默认值
|
||||
/// </summary>
|
||||
const string? DefaultCustomDohAddres = null;
|
||||
const string? DefaultCustomDohAddres2 = null;
|
||||
|
||||
/// <summary>
|
||||
/// 加速页面 Tab 选项卡选中下标的默认值
|
||||
|
||||
@@ -195,7 +195,7 @@ public sealed partial class ProxySettings_ : IProxySettings, ISettings, ISetting
|
||||
/// 自定义 DNS over HTTPS 地址
|
||||
/// </summary>
|
||||
[MPKey(22), MP2Key(22), JsonPropertyOrder(22)]
|
||||
public string? CustomDohAddres { get; set; } = IProxySettings.DefaultCustomDohAddres;
|
||||
public string? CustomDohAddres2 { get; set; } = IProxySettings.DefaultCustomDohAddres2;
|
||||
|
||||
/// <summary>
|
||||
/// 加速页面 Tab 选项卡选中下标
|
||||
@@ -368,8 +368,8 @@ public static partial class ProxySettings
|
||||
/// <summary>
|
||||
/// 自定义 DNS over HTTPS 地址
|
||||
/// </summary>
|
||||
public static SettingsProperty<string, ProxySettings_> CustomDohAddres { get; }
|
||||
= new(DefaultCustomDohAddres);
|
||||
public static SettingsProperty<string, ProxySettings_> CustomDohAddres2 { get; }
|
||||
= new(DefaultCustomDohAddres2);
|
||||
|
||||
/// <summary>
|
||||
/// 加速页面 Tab 选项卡选中下标
|
||||
|
||||
@@ -1,22 +1,45 @@
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
using BD.WTTS.UI.Views.Pages;
|
||||
using STUN.StunResult;
|
||||
using STUN.Enums;
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
public sealed partial class AcceleratorPageViewModel
|
||||
{
|
||||
public NetworkCheckControlViewModel NetworkCheckControlViewModel { get; } = new();
|
||||
|
||||
DateTime _initializeTime;
|
||||
readonly IHostsFileService? hostsFileService;
|
||||
readonly IPlatformService platformService = IPlatformService.Instance;
|
||||
readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance;
|
||||
readonly ICertificateManager certificateManager = ICertificateManager.Constants.Instance;
|
||||
readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public AcceleratorPageViewModel()
|
||||
{
|
||||
ProxyService.Current.WhenValueChanged(x => x.ProxyStatus)
|
||||
.Where(x => x == true)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
// Create new ProxyEnableDomain for 加速服务 page
|
||||
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
|
||||
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
|
||||
.Select(list => new ProxyDomainGroupViewModel
|
||||
{
|
||||
Name = list.Name,
|
||||
IconUrl = list.IconUrl ?? string.Empty,
|
||||
EnableProxyDomainVMs = new(
|
||||
list.Items!
|
||||
.Where(i => i.ThreeStateEnable == true)
|
||||
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
|
||||
i.Items?
|
||||
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
|
||||
.ToList()))
|
||||
.ToList()),
|
||||
})
|
||||
.ToList();
|
||||
|
||||
EnableProxyDomainGroupVMs = enableGroupDomain.AsReadOnly();
|
||||
});
|
||||
|
||||
StartProxyCommand = ReactiveCommand.CreateFromTask(async _ =>
|
||||
{
|
||||
#if LINUX
|
||||
@@ -24,29 +47,6 @@ public sealed partial class AcceleratorPageViewModel
|
||||
#endif
|
||||
//ProxyService.Current.ProxyStatus = !ProxyService.Current.ProxyStatus;
|
||||
await ProxyService.Current.StartOrStopProxyService(!ProxyService.Current.ProxyStatus);
|
||||
|
||||
if (ProxyService.Current.ProxyStatus == false)
|
||||
return;
|
||||
|
||||
// Create new ProxyEnableDomain for 加速服务 page
|
||||
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
|
||||
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
|
||||
.Select(list => new ProxyDomainGroupViewModel
|
||||
{
|
||||
Name = list.Name,
|
||||
IconUrl = list.IconUrl ?? string.Empty,
|
||||
EnableProxyDomainVMs = new(
|
||||
list.Items!
|
||||
.Where(i => i.ThreeStateEnable == true)
|
||||
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
|
||||
i.Items?
|
||||
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
|
||||
.ToList()))
|
||||
.ToList()),
|
||||
})
|
||||
.ToList();
|
||||
|
||||
EnableProxyDomainGroupVMs = enableGroupDomain.AsReadOnly();
|
||||
});
|
||||
|
||||
RefreshCommand = ReactiveCommand.Create(async () =>
|
||||
@@ -69,61 +69,6 @@ public sealed partial class AcceleratorPageViewModel
|
||||
GameAcceleratorService.Current.LoadGames();
|
||||
});
|
||||
|
||||
SelectedSTUNAddress = STUNAddress[0];
|
||||
|
||||
NATCheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var natCheckResult = await networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown };
|
||||
var (netSucc, _) = await networkTestService.TestOpenUrlAsync("https://www.baidu.com");
|
||||
|
||||
var natStatus = natCheckResult.NatType switch
|
||||
{
|
||||
NatType.OpenInternet or NatType.FullCone => NatTypeSimple.Open,
|
||||
NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => NatTypeSimple.Moderate,
|
||||
NatType.Symmetric or NatType.UdpBlocked => NatTypeSimple.Strict,
|
||||
NatType.Unknown or NatType.UnsupportedServer or _ => NatTypeSimple.Unknown,
|
||||
};
|
||||
|
||||
PublicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown";
|
||||
LocalEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown";
|
||||
|
||||
return (natStatus, netSucc);
|
||||
});
|
||||
NATCheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsNATChecking);
|
||||
|
||||
DNSCheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var testDomain = DomainPendingTest == string.Empty ? "steamcommunity.com" : DomainPendingTest;
|
||||
try
|
||||
{
|
||||
long delayMs;
|
||||
IPAddress[] address;
|
||||
if (ProxySettings.UseDoh)
|
||||
{
|
||||
var configDoh = ProxySettings.CustomDohAddres.Value ?? string.Empty;
|
||||
(delayMs, address) = await networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
|
||||
}
|
||||
else
|
||||
{
|
||||
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
|
||||
(delayMs, address) = await networkTestService.TestDNSAsync(testDomain, configDns, 53);
|
||||
}
|
||||
DNSTestDelay = delayMs + "ms ";
|
||||
DNSTestResult = "" + address.FirstOrDefault() ?? "0.0.0.0";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(AcceleratorPageViewModel), ex.ToString());
|
||||
DNSTestDelay = string.Empty;
|
||||
DNSTestResult = "error";
|
||||
}
|
||||
});
|
||||
DNSCheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsDNSChecking);
|
||||
|
||||
ProxySettingsCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var vm = new ProxySettingsWindowViewModel();
|
||||
|
||||
-131
@@ -1,145 +1,14 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using System.Reactive;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
public class ProxyDomainGroupViewModel : ReactiveObject
|
||||
{
|
||||
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string IconUrl { get; set; } = string.Empty;
|
||||
|
||||
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
|
||||
|
||||
public ProxyDomainGroupViewModel()
|
||||
{
|
||||
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var tasks = EnableProxyDomainVMs!.Select(async enableDomain =>
|
||||
{
|
||||
enableDomain.DelayMillseconds = "- ms";
|
||||
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
|
||||
enableDomain.DelayMillseconds = success switch
|
||||
{
|
||||
true when delayMs > 20000 => "Timeout",
|
||||
true => delayMs.ToString() + " ms",
|
||||
false => "error",
|
||||
};
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ProxyDomainViewModel : ReactiveObject
|
||||
{
|
||||
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public ProxyType ProxyType { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
[Reactive]
|
||||
public string DelayMillseconds { get; set; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool RetestButtonVisible { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public IBrush DelayColor { get; } = null!;
|
||||
|
||||
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
|
||||
|
||||
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
|
||||
{
|
||||
Name = name;
|
||||
ProxyType = proxyType;
|
||||
Url = url;
|
||||
Children = children?.AsReadOnly();
|
||||
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
|
||||
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Select(d => d != string.Empty && d != "-")
|
||||
.ToPropertyEx(this, x => x.RetestButtonVisible);
|
||||
|
||||
const int DelayMiddle = 1000;
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Select(d => d switch
|
||||
{
|
||||
"Timeout" or "error" => Brushes.Red,
|
||||
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
|
||||
=> ms switch
|
||||
{
|
||||
<= DelayMiddle => Brushes.Green,
|
||||
> DelayMiddle => Brushes.Orange,
|
||||
},
|
||||
_ => (IBrush?)App.Instance.FindResource("TextFillColorPrimaryBrush"),
|
||||
})
|
||||
.ToPropertyEx(this, x => x.DelayColor);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class AcceleratorPageViewModel : TabItemViewModel
|
||||
{
|
||||
public enum NatTypeSimple
|
||||
{
|
||||
Unknown,
|
||||
Open,
|
||||
Moderate,
|
||||
Strict,
|
||||
}
|
||||
|
||||
public override string Name => Strings.Welcome;
|
||||
|
||||
[Reactive]
|
||||
public string SelectedSTUNAddress { get; set; }
|
||||
|
||||
public List<string> STUNAddress { get; } =
|
||||
[
|
||||
"stun.syncthing.net",
|
||||
"stun.hot-chilli.net",
|
||||
"stun.fitauto.ru",
|
||||
"stun.miwifi.com",
|
||||
];
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsNATChecking { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsDNSChecking { get; }
|
||||
|
||||
[Reactive]
|
||||
public string DomainPendingTest { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public ReadOnlyCollection<ProxyDomainGroupViewModel>? EnableProxyDomainGroupVMs { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string LocalEndPoint { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string PublicEndPoint { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string DNSTestDelay { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string DNSTestResult { get; set; } = string.Empty;
|
||||
|
||||
public ReactiveCommand<Unit, (NatTypeSimple Nat, bool PingSuccess)> NATCheckCommand { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> DNSCheckCommand { get; }
|
||||
|
||||
public ICommand StartProxyCommand { get; }
|
||||
|
||||
public ICommand RefreshCommand { get; }
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
using STUN.Enums;
|
||||
using STUN.StunResult;
|
||||
using System.Reactive;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
public partial class NetworkCheckControlViewModel : ViewModelBase
|
||||
{
|
||||
[GeneratedRegex(DomainValidationAttribute.RegPattern)]
|
||||
private static partial Regex DomainRegExp();
|
||||
|
||||
public class DomainValidationAttribute : RegularExpressionAttribute
|
||||
{
|
||||
public const string RegPattern = @"^[^/]+\.[^/]{2,}$";
|
||||
|
||||
public override string FormatErrorMessage(string name) => "请填入不带分隔符 \"/\" 的域名";
|
||||
|
||||
public DomainValidationAttribute() : base(RegPattern)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private readonly INetworkTestService _networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public record NATFetchResult(string PublicEndPoint, string LocalEndPoint, string NATLevel, string NATTypeTip, bool PingResult);
|
||||
|
||||
public string DefaultTestDomain { get; } = "store.steampowered.com";
|
||||
|
||||
[Reactive]
|
||||
public string SelectedSTUNAddress { get; set; }
|
||||
|
||||
public string[] STUNAddress { get; } =
|
||||
[
|
||||
"stun.syncthing.net",
|
||||
"stun.hot-chilli.net",
|
||||
"stun.fitauto.ru",
|
||||
"stun.miwifi.com",
|
||||
];
|
||||
|
||||
[ObservableAsProperty]
|
||||
public string NATLevel { get; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public string NATTypeTip { get; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public string LocalEndPoint { get; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public string PublicEndPoint { get; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool PingOkVisible { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool PingErrorVisible { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsNATChecking { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsDNSChecking { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsIPv6Checking { get; }
|
||||
|
||||
[DomainValidation]
|
||||
[Reactive]
|
||||
public string DomainPendingTest { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string DNSTestDelay { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string DNSTestResult { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string IPv6Address { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public bool IsSupportIPv6 { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, NATFetchResult> NATCheckCommand { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> DNSCheckCommand { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> IPv6CheckCommand { get; }
|
||||
|
||||
public NetworkCheckControlViewModel()
|
||||
{
|
||||
SelectedSTUNAddress = STUNAddress[0];
|
||||
|
||||
NATCheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var natCheckResult = await _networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown };
|
||||
var (netSucc, _) = await _networkTestService.TestOpenUrlAsync("https://www.baidu.com");
|
||||
|
||||
var publicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown";
|
||||
var localEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown";
|
||||
|
||||
var (natLevel, natTypeTip) = natCheckResult.NatType switch
|
||||
{
|
||||
// Open
|
||||
NatType.OpenInternet or NatType.FullCone => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"),
|
||||
// Moderate
|
||||
NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"),
|
||||
// Strict
|
||||
NatType.Symmetric or NatType.UdpBlocked => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"),
|
||||
// Unknown
|
||||
NatType.Unknown or NatType.UnsupportedServer or _ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"),
|
||||
};
|
||||
|
||||
return new NATFetchResult(publicEndPoint, localEndPoint, natLevel, natTypeTip, netSucc);
|
||||
});
|
||||
NATCheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsNATChecking);
|
||||
NATCheckCommand
|
||||
.Select(x => x.PublicEndPoint)
|
||||
.ToPropertyEx(this, x => x.PublicEndPoint);
|
||||
NATCheckCommand
|
||||
.Select(x => x.LocalEndPoint)
|
||||
.ToPropertyEx(this, x => x.LocalEndPoint);
|
||||
NATCheckCommand
|
||||
.Select(x => x.NATLevel)
|
||||
.Merge(Observable.Return("未知"))
|
||||
.ToPropertyEx(this, x => x.NATLevel);
|
||||
NATCheckCommand
|
||||
.Select(x => x.NATTypeTip)
|
||||
.Merge(Observable.Return("未知类型"))
|
||||
.ToPropertyEx(this, x => x.NATTypeTip);
|
||||
|
||||
var hidePingResultStream = NATCheckCommand
|
||||
.IsExecuting
|
||||
.Where(x => x == true)
|
||||
.Select(x => false);
|
||||
NATCheckCommand
|
||||
.Select(x => x.PingResult == true)
|
||||
.Merge(hidePingResultStream)
|
||||
.ToPropertyEx(this, x => x.PingOkVisible);
|
||||
NATCheckCommand
|
||||
.Select(x => x.PingResult == false)
|
||||
.Merge(hidePingResultStream)
|
||||
.ToPropertyEx(this, x => x.PingErrorVisible);
|
||||
|
||||
var canDNSCheck = this.WhenAnyValue(x => x.DomainPendingTest)
|
||||
.Select(domain => domain == string.Empty || DomainRegExp().IsMatch(domain));
|
||||
DNSCheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var testDomain = DomainPendingTest == string.Empty ? DefaultTestDomain : DomainPendingTest;
|
||||
try
|
||||
{
|
||||
long delayMs;
|
||||
IPAddress[] address;
|
||||
if (ProxySettings.UseDoh)
|
||||
{
|
||||
var configDoh = ProxySettings.CustomDohAddres2.Value ?? ProxySettingsWindowViewModel.DohAddress.FirstOrDefault() ?? string.Empty;
|
||||
(delayMs, address) = await _networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
|
||||
}
|
||||
else
|
||||
{
|
||||
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
|
||||
(delayMs, address) = await _networkTestService.TestDNSAsync(testDomain, configDns, 53);
|
||||
}
|
||||
if (address.Length == 0)
|
||||
throw new Exception("Parsing failed. Return empty ip address.");
|
||||
|
||||
DNSTestDelay = delayMs + "ms ";
|
||||
DNSTestResult = string.Empty + address.FirstOrDefault();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(AcceleratorPageViewModel), ex.ToString());
|
||||
DNSTestDelay = string.Empty;
|
||||
DNSTestResult = "error";
|
||||
}
|
||||
}, canDNSCheck);
|
||||
DNSCheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsDNSChecking);
|
||||
|
||||
IPv6CheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var result = await IMicroServiceClient.Instance.Accelerate.GetMyIP(ipV6: true);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
IsSupportIPv6 = true;
|
||||
IPv6Address = result.Content ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsSupportIPv6 = false;
|
||||
IPv6Address = string.Empty;
|
||||
}
|
||||
});
|
||||
IPv6CheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsIPv6Checking);
|
||||
|
||||
IPv6CheckCommand.Execute().Subscribe();
|
||||
}
|
||||
}
|
||||
@@ -44,4 +44,22 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
|
||||
DohAddres_360,
|
||||
TUNA_DohAddres,
|
||||
};
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
// 更改多个设置项不立即保存
|
||||
ProxySettings.SystemProxyIp.Reset(save: false);
|
||||
ProxySettings.ProxyMasterDns.Reset(save: false);
|
||||
ProxySettings.SystemProxyPortId.Reset(save: false);
|
||||
ProxySettings.ProgramStartupRunProxy.Reset(save: false);
|
||||
ProxySettings.EnableHttpProxyToHttps.Reset(save: false);
|
||||
ProxySettings.UseDoh.Reset(save: false);
|
||||
ProxySettings.CustomDohAddres2.Reset(save: false);
|
||||
ProxySettings.OnlyEnableProxyScript.Reset(save: false);
|
||||
|
||||
// 更改完成后保存一次
|
||||
ProxySettings.OnlyEnableProxyScript.Save();
|
||||
|
||||
Toast.Show(ToastIcon.Success, "重置成功");
|
||||
}
|
||||
}
|
||||
+3
-7
@@ -24,7 +24,7 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DataTemplate x:DataType="spp:ProxyDomainGroupViewModel">
|
||||
<Border>
|
||||
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
|
||||
<ui:SettingsExpander.Header>
|
||||
@@ -49,11 +49,6 @@
|
||||
</ui:SettingsExpander.Footer>
|
||||
<ui:SettingsExpanderItem Theme="{StaticResource SettingsExpanderItem2}">
|
||||
<TreeView ItemsSource="{Binding EnableProxyDomainVMs}" SelectionMode="Single">
|
||||
<TreeView.ItemContainerTheme>
|
||||
<ControlTheme BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="TreeViewItem">
|
||||
<Setter Property="IsExpanded" Value="True" />
|
||||
</ControlTheme>
|
||||
</TreeView.ItemContainerTheme>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||
<DockPanel Margin="8,0">
|
||||
@@ -82,7 +77,8 @@
|
||||
DockPanel.Dock="Right"
|
||||
Foreground="{Binding DelayColor}"
|
||||
Text="{Binding DelayMillseconds}"
|
||||
ToolTip.Tip="此数值代表测试网页打开访问所需时间,仅供参考" />
|
||||
ToolTip.ShowDelay="0"
|
||||
ToolTip.Tip="此数值代表测试网页打开访问所需时间,请以实际浏览器访问网页状况为准" />
|
||||
</DockPanel>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
|
||||
@@ -11,36 +11,26 @@
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="spp:AcceleratorPageViewModel"
|
||||
x:DataType="spp:NetworkCheckControlViewModel"
|
||||
mc:Ignorable="d">
|
||||
<ScrollViewer
|
||||
Padding="0,0,10,0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8">
|
||||
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
|
||||
<ui:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="26"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="NAT 类型检测" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Header>
|
||||
<!-- NAT -->
|
||||
<ui:SettingsExpander Header="NAT 类型检测" IsExpanded="True">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<WrapPanel>
|
||||
<ui:FAComboBox
|
||||
x:Name="STUNServer"
|
||||
Margin="4"
|
||||
x:FieldModifier="public"
|
||||
Margin="0,0,4,4"
|
||||
IsEditable="False"
|
||||
IsEnabled="{Binding !IsNATChecking}"
|
||||
ItemsSource="{Binding STUNAddress}"
|
||||
SelectedItem="{Binding SelectedSTUNAddress}"
|
||||
SelectedItem="{Binding SelectedSTUNAddress, Mode=TwoWay}"
|
||||
ToolTip.Tip="STUN 服务器地址">
|
||||
<ui:FAComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="sys:String">
|
||||
@@ -51,257 +41,333 @@
|
||||
<Button
|
||||
x:Name="NATCheckButton"
|
||||
Width="100"
|
||||
Margin="4"
|
||||
x:FieldModifier="public"
|
||||
Margin="0,0,0,4"
|
||||
Command="{Binding NATCheckCommand}"
|
||||
Content="检测"
|
||||
IsEnabled="True" />
|
||||
</WrapPanel>
|
||||
</ui:SettingsExpander.Footer>
|
||||
|
||||
<StackPanel
|
||||
Margin="24,0,0,0"
|
||||
Background="Transparent"
|
||||
Spacing="8">
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center" Text="NAT 类型:" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<ui:SettingsExpanderItem Content="NAT 类型:">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
|
||||
<DockPanel IsVisible="{Binding !IsNATChecking}">
|
||||
<SelectableTextBlock
|
||||
x:Name="NATTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="Gray"
|
||||
Text="未知" />
|
||||
<ui:FontIcon
|
||||
Margin="3,0"
|
||||
FontSize="20"
|
||||
Glyph="">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock x:Name="NATTypeTip" Text="未知类型" />
|
||||
</ToolTip.Tip>
|
||||
</ui:FontIcon>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center" Text="本地IP地址:" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<DockPanel IsVisible="{Binding !IsNATChecking}">
|
||||
<SelectableTextBlock
|
||||
x:Name="LocalIPAddress"
|
||||
x:Name="NATTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsNATChecking}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="对外IP地址:"
|
||||
ToolTip.Tip="若与您路由器 WAN IP 地址相同,则为公共网络" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<SelectableTextBlock
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsNATChecking}"
|
||||
Text="{Binding PublicEndPoint}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
|
||||
<!--<Border Classes="paper" Margin="-64 0" />-->
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center" Text="互联网连通情况:" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
|
||||
Text="{Binding NATLevel}" />
|
||||
<ui:FontIcon
|
||||
Margin="3,0"
|
||||
FontSize="20"
|
||||
Glyph=""
|
||||
ToolTip.ShowDelay="0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock x:Name="NATTypeTip" Text="{Binding NATTypeTip}" />
|
||||
</ToolTip.Tip>
|
||||
</ui:FontIcon>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Content="本机IP地址:">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<SelectableTextBlock
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsNATChecking}"
|
||||
Text="{Binding LocalEndPoint}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="对外IP地址:"
|
||||
ToolTip.Tip="若与您路由器 WAN IP 地址相同,则为公共网络" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<SelectableTextBlock
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsNATChecking}"
|
||||
Text="{Binding PublicEndPoint}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Content="互联网连通情况:">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
|
||||
<ui:FontIcon
|
||||
x:Name="PingOK"
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="26"
|
||||
Foreground="Green"
|
||||
Glyph=""
|
||||
IsVisible="{Binding PingOkVisible}" />
|
||||
|
||||
<Grid x:Name="PingError" IsVisible="{Binding PingErrorVisible}">
|
||||
<ui:FontIcon
|
||||
Margin="3"
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="20"
|
||||
Foreground="Red"
|
||||
Glyph="" />
|
||||
<ui:FontIcon
|
||||
x:Name="PingOK"
|
||||
HorizontalAlignment="Right"
|
||||
x:FieldModifier="public"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="26"
|
||||
Foreground="Green"
|
||||
Glyph="" />
|
||||
|
||||
<Grid x:Name="PingError" x:FieldModifier="public">
|
||||
<ui:FontIcon
|
||||
Margin="3"
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="20"
|
||||
Foreground="Red"
|
||||
Glyph="" />
|
||||
<ui:FontIcon
|
||||
HorizontalAlignment="Right"
|
||||
FontSize="26"
|
||||
Foreground="White"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
Foreground="White"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<!--<Separator Classes="Separator" Margin="-64 0" />-->
|
||||
<Separator Margin="-64,0" Classes="Horizontal" />
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock
|
||||
Margin="-28,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="相关链接" />
|
||||
|
||||
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="相关链接" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="解决NAT错误"
|
||||
NavigateUri="https://support.xbox.com/zh-CN/help/hardware-network/connect-network/xbox-one-nat-error" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpanderItem>
|
||||
<WrapPanel Margin="-10,0,0,0">
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="XBOX 关于 NAT 文档"
|
||||
NavigateUri="https://support.xbox.com/zh-CN/help/hardware-network/connect-network/xbox-one-nat-error" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="PlayStation 关于 NAT 文档"
|
||||
NavigateUri="https://manuals.playstation.net/document/ct/ps4/settings/nw_test.html" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="Nintendo Switch 关于 NAT 文档"
|
||||
NavigateUri="https://www.nintendoswitch.com.cn/support/faq/373.html" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="Bungie 关于 NAT 文档"
|
||||
NavigateUri="https://help.bungie.net/hc/zh-cn/articles/360049496751-%E9%AB%98%E7%BA%A7%E7%96%91%E9%9A%BE%E6%8E%92%E8%A7%A3-%E9%80%9A%E7%94%A8%E5%8D%B3%E6%8F%92%E5%8D%B3%E7%94%A8-%E7%AB%AF%E5%8F%A3%E8%BD%AC%E9%80%81%E4%B8%8E%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="Activision 关于 NAT 文档"
|
||||
NavigateUri="https://support.activision.com/tw/zh/articles/port-forward-and-nat-faq" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="RockStar Games 关于 NAT 文档"
|
||||
NavigateUri="https://support.rockstargames.com/tw/articles/206210548/%E5%A6%82%E4%BD%95%E8%A7%A3%E6%B1%BA-GTA-%E7%B7%9A%E4%B8%8A%E6%A8%A1%E5%BC%8F%E4%B8%AD%E8%88%87%E5%9A%B4%E6%A0%BC-Strict-NAT-%E9%A1%9E%E5%9E%8B%E7%9B%B8%E9%97%9C%E7%9A%84%E9%8C%AF%E8%AA%A4" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="迅游关于 NAT 文档"
|
||||
NavigateUri="https://cs.xunyou.com/html/282/15271.shtml" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<!-- DNS -->
|
||||
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
|
||||
<ui:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="26"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="DNS 检测" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Header>
|
||||
<ui:SettingsExpander Header="DNS 检测" IsExpanded="True">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<Button
|
||||
x:Name="DNSCheckButton"
|
||||
Width="100"
|
||||
Content="检测"
|
||||
IsEnabled="True" />
|
||||
Command="{Binding DNSCheckCommand}"
|
||||
Content="检测" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
|
||||
<StackPanel Margin="24,0,0,0">
|
||||
<TextBlock
|
||||
Margin="-28,0,0,-4"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="检测项目" />
|
||||
|
||||
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="检测项目" />
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem Content="待检测的域名">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBox
|
||||
Width="220"
|
||||
Text="{Binding DomainPendingTest, Mode=TwoWay}"
|
||||
Watermark="{Binding DefaultTestDomain}">
|
||||
<TextBox.Styles>
|
||||
<Style Selector="DataValidationErrors">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<DockPanel LastChildFill="True">
|
||||
<ContentControl
|
||||
Content="{Binding (DataValidationErrors.Errors)}"
|
||||
ContentTemplate="{TemplateBinding ErrorTemplate}"
|
||||
DataContext="{TemplateBinding Owner}"
|
||||
DockPanel.Dock="Right"
|
||||
IsVisible="{Binding (DataValidationErrors.HasErrors)}" />
|
||||
<ContentPresenter
|
||||
Name="PART_ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||
</DockPanel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Setter Property="ErrorTemplate">
|
||||
<DataTemplate x:DataType="{x:Type x:Object}">
|
||||
<Canvas
|
||||
Width="14"
|
||||
Height="14"
|
||||
Margin="4,0,1,0"
|
||||
Background="Transparent">
|
||||
<ToolTip.Tip>
|
||||
<ItemsControl ItemsSource="{Binding}" />
|
||||
</ToolTip.Tip>
|
||||
<Path
|
||||
Data="M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2"
|
||||
Stroke="Red"
|
||||
StrokeThickness="2" />
|
||||
</Canvas>
|
||||
</DataTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
</TextBox.Styles>
|
||||
</TextBox>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
<ui:SettingsExpanderItem Content="{Binding Path=Res.Settings_Proxy_CustomDNS, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
Width="220"
|
||||
IsEditable="True"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
|
||||
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.ProxyDNSs}}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="待检测的域名" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBox
|
||||
x:Name="DomainTextBox"
|
||||
Width="200"
|
||||
Watermark="steamcommunity.com" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem Content="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_CustomDNS, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
x:Name="dummy"
|
||||
Width="200"
|
||||
IsEditable="False"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
|
||||
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.ProxyDNSs}}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_CustomDohAddres, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
Width="220"
|
||||
IsEditable="True"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
|
||||
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.DohAddress}}"
|
||||
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres2}, Mode=OneWay}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres2}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
<TextBlock
|
||||
Margin="-28,0,0,-4"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="测试结果" />
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_CustomDohAddres, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
Width="200"
|
||||
IsEditable="False"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
|
||||
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.DohAddress}}"
|
||||
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres}, Mode=OneWay}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem Content="延迟">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsDNSChecking}" />
|
||||
<SelectableTextBlock IsVisible="{Binding !IsDNSChecking}" Text="{Binding DNSTestDelay}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="测试结果" />
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem Content="解析地址">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsDNSChecking}" />
|
||||
<SelectableTextBlock IsVisible="{Binding !IsDNSChecking}" Text="{Binding DNSTestResult}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
<!-- IPV6 -->
|
||||
<ui:SettingsExpander Header="IPv6 检测" IsExpanded="True">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<Button
|
||||
Width="100"
|
||||
Command="{Binding IPv6CheckCommand}"
|
||||
Content="检测" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="延迟" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsDNSChecking}" />
|
||||
<SelectableTextBlock Text="{Binding DNSTestDelay}" />
|
||||
<ui:SettingsExpanderItem Content="支持情况">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsIPv6Checking}" />
|
||||
<Grid IsVisible="{Binding !IsIPv6Checking}">
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
|
||||
IsVisible="{Binding IsSupportIPv6}"
|
||||
Text="已支持 IPv6 访问" />
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||
IsVisible="{Binding !IsSupportIPv6}"
|
||||
Text="不支持 IPv6 访问" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="解析地址" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsDNSChecking}" />
|
||||
<SelectableTextBlock Text="{Binding DNSTestResult}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
</StackPanel>
|
||||
<ui:SettingsExpanderItem Content="IPv6 地址">
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsIPv6Checking}" />
|
||||
<SelectableTextBlock
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsIPv6Checking}"
|
||||
Text="{Binding IPv6Address}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Org.BouncyCastle.Asn1.Utilities;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Pages;
|
||||
|
||||
@@ -10,8 +7,5 @@ public partial class NetworkCheck : UserControl
|
||||
public NetworkCheck()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.dummy.SelectedItem = ProxySettings.ProxyMasterDns.Value;
|
||||
this.dummy.Text = ProxySettings.ProxyMasterDns.Value;
|
||||
}
|
||||
}
|
||||
@@ -238,28 +238,7 @@
|
||||
</TabStrip.ItemTemplate>
|
||||
</TabStrip>
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock Text="{x:Static spp:Strings.CommunityFix_MenuName}" Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Classes.NotSupport="{Binding Source={x:Static s:ProxyService.Current}, Path=IPv6AddresString, Mode=OneWay, Converter={StaticResource IsNullConverter}}"
|
||||
Classes.Support="{Binding Source={x:Static s:ProxyService.Current}, Path=IPv6AddresString, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
|
||||
Theme="{StaticResource CaptionTextBlockStyle}"
|
||||
ToolTip.Tip="{Binding Source={x:Static s:ProxyService.Current}, Path=IPv6AddresString, Mode=OneWay}">
|
||||
<TextBlock.Styles>
|
||||
<Style Selector="TextBlock.Support">
|
||||
<Setter Property="Text" Value="已支持 IPv6 访问" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.NotSupport">
|
||||
<Setter Property="Text" Value="不支持 IPv6 访问" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}" />
|
||||
</Style>
|
||||
</TextBlock.Styles>
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
<TextBlock Text="{x:Static spp:Strings.CommunityFix_MenuName}" Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
|
||||
<ToggleSwitch
|
||||
VerticalAlignment="Center"
|
||||
@@ -424,7 +403,7 @@
|
||||
</spp:PageBase.PaneContent>
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,*">
|
||||
<Grid.Resources>
|
||||
<sys:Double x:Key="AdaptiveWidth">800</sys:Double>
|
||||
<sys:Double x:Key="AdaptiveWidth">900</sys:Double>
|
||||
</Grid.Resources>
|
||||
<Grid.Styles>
|
||||
<Style Selector="StackPanel.small">
|
||||
@@ -669,8 +648,9 @@
|
||||
</Panel>
|
||||
|
||||
<!-- 网络检测 -->
|
||||
<spp:NetworkCheck x:Name="NetworkCheckControl" />
|
||||
<spp:NetworkCheck DataContext="{Binding NetworkCheckControlViewModel}" />
|
||||
|
||||
<TabItem Content="dummy_tab_for_transisit_animation" />
|
||||
</Carousel>
|
||||
|
||||
<!-- Side tab of paid game spped up -->
|
||||
@@ -805,12 +785,20 @@
|
||||
<Run Text="A:流量统计图表是用于查看实时流量数据信息,您需要先访问对应的网站,这边才会有相应的流量波动数据,请检查是否已经选择了对应的网站加速项目,如果确定有正常勾选加速项目并且访问网站依然无流量,则可能是加速的监听服务没有启用成功,尝试更换加速模式。" />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Run FontSize="16" Text="Q:加速后对应网站打不开显示SSL错误或者Steam浏览器显示-100错误?" />
|
||||
<Run FontSize="16" Text="Q:加速后对应网站打不开显示 SSL 错误或者 Steam 浏览器显示 -100 错误?" />
|
||||
<LineBreak />
|
||||
<Run Text="A:可以尝试执行以下操作进行修复,关闭加速,点击加速设置-移除证书再安装证书,重开加速。或者加速设置-代理设置-尝试更换doh地址或者关闭doh使用dns等方案。" />
|
||||
<Run Text="A:可以尝试执行以下操作进行修复,关闭加速,点击加速设置-移除证书再安装证书,重开加速。或者加速设置 - 代理设置- 尝试更换 doh 地址或者关闭 doh 使用 dns 等方案。" />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Run FontSize="16" Text="Q:为什么加速经常不可用?还要自己去改设置或重新安装证书?" />
|
||||
<LineBreak />
|
||||
<Run Text="A:Steam++ 的加速与游戏加速器的加速功能不同,加速是完全运行于你电脑本地的,所以会受到你电脑本地网络环境,系统环境各种因素影响,也正因如此才有这么多加速相关的选项,请合理根据情况适进行测试以及更改对应的选项以便加速能正常使用。" />
|
||||
</TextBlock>
|
||||
</ToolTip.Tip>
|
||||
<ui:FontIcon FontSize="20" Glyph="" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<ui:FontIcon FontSize="20" Glyph="" />
|
||||
<TextBlock Text="加速不能用?" />
|
||||
</StackPanel>
|
||||
</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}}" />
|
||||
|
||||
@@ -73,36 +73,6 @@ public partial class AcceleratorPage2 : PageBase<AcceleratorPageViewModel>
|
||||
GameAccTab.IsVisible = false;
|
||||
AcceleratorTabs.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSTUNAddress, v => v.NetworkCheckControl.STUNServer.SelectedItem).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.NATCheckCommand, v => v.NetworkCheckControl.NATCheckButton).DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.LocalEndPoint, v => v.NetworkCheckControl.LocalIPAddress.Text).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.DNSCheckCommand, v => v.NetworkCheckControl.DNSCheckButton).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DomainPendingTest, v => v.NetworkCheckControl.DomainTextBox.Text).DisposeWith(disposables);
|
||||
|
||||
NetworkCheckControl.PingOK.IsVisible = false;
|
||||
NetworkCheckControl.PingError.IsVisible = false;
|
||||
NetworkCheckControl.NATCheckButton.Click += (_, _) => NetworkCheckControl.PingError.IsVisible = NetworkCheckControl.PingOK.IsVisible = false;
|
||||
ViewModel!.NATCheckCommand
|
||||
.Subscribe(result =>
|
||||
{
|
||||
(NetworkCheckControl.NATTextBlock.Text, NetworkCheckControl.NATTypeTip.Text) = result.Nat switch
|
||||
{
|
||||
AcceleratorPageViewModel.NatTypeSimple.Open => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"),
|
||||
AcceleratorPageViewModel.NatTypeSimple.Moderate => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"),
|
||||
AcceleratorPageViewModel.NatTypeSimple.Strict => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"),
|
||||
_ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"),
|
||||
};
|
||||
|
||||
if (result.PingSuccess)
|
||||
{
|
||||
NetworkCheckControl.PingOK.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkCheckControl.PingError.IsVisible = true;
|
||||
}
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
SearchGameBox.DropDownClosed += SearchGameBox_DropDownClosed;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
mc:Ignorable="d">
|
||||
<StackPanel Margin="{StaticResource PageMargin}" Spacing="{StaticResource SettingsSpacing}">
|
||||
<ui:SettingsExpander Header="{Binding Path=Res.CommunityFix_ProxySettings, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" IsExpanded="True">
|
||||
|
||||
<ui:SettingsExpander.Footer>
|
||||
<Button Command="{Binding ResetSettings}" Content="{Binding Path=Res.Reset, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
<ui:SettingsExpanderItem>
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_SystemProxyIp, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
@@ -31,6 +33,7 @@
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
IsEditable="True"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
|
||||
ItemsSource="{Binding ProxyDNSs}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
@@ -78,9 +81,10 @@
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
IsEditable="True"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
|
||||
ItemsSource="{Binding DohAddress}"
|
||||
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres}, Mode=OneWay}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres}, Path=Value, Mode=TwoWay}" />
|
||||
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres2}, Mode=OneWay}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres2}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
Description="{Binding Description, Source={x:Static s:Plugin.Instance}, Mode=OneWay}"
|
||||
Subtitle="{Binding Author, Source={x:Static s:Plugin.Instance}, Mode=OneWay, Converter={StaticResource StringFormatConverter}, ConverterParameter=Plugin_Author}"
|
||||
mc:Ignorable="d">
|
||||
<spp:PageBase.Styles>
|
||||
<Style Selector="spp|AppItem /template/ TextBlock#HeaderText">
|
||||
<Setter Property="Grid.ColumnSpan" Value="0" />
|
||||
</Style>
|
||||
</spp:PageBase.Styles>
|
||||
<spp:PageBase.PreviewImage>
|
||||
<ui:ImageIconSource Source="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/script.ico" />
|
||||
</spp:PageBase.PreviewImage>
|
||||
|
||||
@@ -113,4 +113,22 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
{
|
||||
yield return GetConfiguration<ASFSettings_>(directoryExists);
|
||||
}
|
||||
|
||||
public override async ValueTask OnExit()
|
||||
{
|
||||
if (ArchiSteamFarmServiceImpl.ASFProcessId.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = Process.GetProcessById(ArchiSteamFarmServiceImpl.ASFProcessId.Value);
|
||||
process.Kill();
|
||||
process.Dispose();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// 进程已经退出
|
||||
}
|
||||
}
|
||||
await base.OnExit();
|
||||
}
|
||||
}
|
||||
|
||||
+40
-34
@@ -27,6 +27,8 @@ public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarm
|
||||
{
|
||||
}
|
||||
|
||||
public static int? ASFProcessId { get; private set; }
|
||||
|
||||
public Process? ASFProcess { get; set; }
|
||||
|
||||
public event Action<string>? OnConsoleWirteLine;
|
||||
@@ -89,26 +91,48 @@ public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarm
|
||||
{
|
||||
var isStartSuccess = false;
|
||||
var ipcUrl = string.Empty;
|
||||
try
|
||||
|
||||
if (ASFProcess != null || string.IsNullOrEmpty(SharedInfo.ASFExecuteFilePath))
|
||||
{
|
||||
if (ASFProcess != null || string.IsNullOrEmpty(SharedInfo.ASFExecuteFilePath))
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, BDStrings.ASF_SelectASFExePath);
|
||||
return (isStartSuccess, ipcUrl);
|
||||
}
|
||||
Toast.Show(ToastIcon.Error, BDStrings.ASF_SelectASFExePath);
|
||||
return (isStartSuccess, ipcUrl);
|
||||
}
|
||||
|
||||
if (ASFSettings.CheckArchiSteamFarmExe && !await CheckFileConsistence()) // 检查文件是否被篡改
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, BDStrings.ASF_ExecuteFileUnsafe);
|
||||
return (isStartSuccess, ipcUrl);
|
||||
}
|
||||
if (ASFSettings.CheckArchiSteamFarmExe && !await CheckFileConsistence()) // 检查文件是否被篡改
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, BDStrings.ASF_ExecuteFileUnsafe);
|
||||
return (isStartSuccess, ipcUrl);
|
||||
}
|
||||
|
||||
ipcUrl = GetIPCUrl();
|
||||
webApiService.SetIPCUrl(ipcUrl); // 设置 IPC 接口地址
|
||||
ipcUrl = GetIPCUrl();
|
||||
webApiService.SetIPCUrl(ipcUrl); // 设置 IPC 接口地址
|
||||
|
||||
KillASFProcess(); // 杀死未关闭的 ASF 进程
|
||||
KillASFProcess(); // 杀死未关闭的 ASF 进程
|
||||
|
||||
var options = new ProcessStartInfo(SharedInfo.ASFExecuteFilePath);
|
||||
ASFProcess = StartAsync(SharedInfo.ASFExecuteFilePath);
|
||||
ASFProcessId = ASFProcess?.Id;
|
||||
|
||||
ASFService.Current.ConsoleLogText = string.Empty;
|
||||
Task2.InBackground(ReadOutPutData, true);
|
||||
AppDomain.CurrentDomain.ProcessExit += ExitHandler;
|
||||
AppDomain.CurrentDomain.UnhandledException += ExitHandler;
|
||||
|
||||
ASFProcess!.ErrorDataReceived += new DataReceivedEventHandler(ExitHandler);
|
||||
ASFProcess.BeginErrorReadLine();
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60));
|
||||
while (
|
||||
!(isStartSuccess = SocketHelper.IsUsePort(CurrentIPCPortValue)) &&
|
||||
!cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
continue;
|
||||
}
|
||||
return (isStartSuccess, ipcUrl);
|
||||
|
||||
Process? StartAsync(string fileName)
|
||||
{
|
||||
var options = new ProcessStartInfo(fileName);
|
||||
options.CreateNoWindow = true;
|
||||
options.UseShellExecute = false;
|
||||
options.RedirectStandardOutput = true;
|
||||
@@ -120,26 +144,8 @@ public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarm
|
||||
options.ArgumentList.Add("--CRYPTKEY");
|
||||
options.ArgumentList.Add(EncryptionKey);
|
||||
}
|
||||
ASFProcess = Process.Start(options);
|
||||
|
||||
ASFService.Current.ConsoleLogText = string.Empty;
|
||||
Task2.InBackground(ReadOutPutData, true);
|
||||
AppDomain.CurrentDomain.ProcessExit += ExitHandler;
|
||||
AppDomain.CurrentDomain.UnhandledException += ExitHandler;
|
||||
|
||||
ASFProcess!.ErrorDataReceived += new DataReceivedEventHandler(ExitHandler);
|
||||
ASFProcess.BeginErrorReadLine();
|
||||
while (!SocketHelper.IsUsePort(CurrentIPCPortValue))
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
isStartSuccess = true;
|
||||
return Process.Start(options);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return (isStartSuccess, ipcUrl);
|
||||
}
|
||||
|
||||
private void KillASFProcess()
|
||||
|
||||
@@ -156,8 +156,20 @@ public sealed class ASFService : ReactiveObject
|
||||
|
||||
public void OpenFolder(string tag)
|
||||
{
|
||||
if (!Enum.TryParse<EPathFolder>(tag, true, out var folderASFPath)) return;
|
||||
var folderASFPathValue = folderASFPath.GetFolderPath();
|
||||
if (string.IsNullOrEmpty(ASFSettings.ArchiSteamFarmExePath.Value))
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, BDStrings.ASF_SetExePathFirst);
|
||||
return;
|
||||
}
|
||||
|
||||
string folderASFPathValue = string.Empty;
|
||||
if (!Enum.TryParse<EPathFolder>(tag, true, out var folderASFPath) ||
|
||||
!Path.Exists(folderASFPathValue = folderASFPath.GetFolderPath()))
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, BDStrings.ASF_SelectASFExePath);
|
||||
return;
|
||||
}
|
||||
|
||||
IPlatformService.Instance.OpenFolder(folderASFPathValue);
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -11,9 +11,16 @@ public partial class ArchiSteamFarmExePathSettingsPageViewModel : ViewModelBase
|
||||
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);
|
||||
if (INotificationService.Instance.IsSupportNotifyDownload)
|
||||
{
|
||||
var progress = INotificationService.Instance.NotifyDownload(() => "开始下载 ASF ", NotificationType.NewVersion);
|
||||
ASFService.Current.DownloadASFAsync(progress: progress);
|
||||
Toast.Show(ToastIcon.Info, "等待后台下载文件", ToastLength.Short);
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Show(ToastIcon.Error, "当前平台无法调用下载通知,请自行下载", ToastLength.Short);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ static class SharedInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetDirectoryName(ASFSettingsExecuteFilePath) ?? string.Empty;
|
||||
return !string.IsNullOrWhiteSpace(ASFSettingsExecuteFilePath)
|
||||
? Path.GetDirectoryName(ASFSettingsExecuteFilePath) ?? string.Empty
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
x:DataType="spp:AuthenticatorItemModel"
|
||||
Classes.IsActive="{Binding IsSelected}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="spp|AppItem /template/ TextBlock#HeaderText">
|
||||
<Setter Property="Grid.ColumnSpan" Value="0" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<spp:AppItem
|
||||
Title="{Binding AuthName}"
|
||||
Description="{Binding AuthData.Created, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
"%LocalAppData%\\Riot Games\\Riot Client\\Data\\Cookies": "Cookies",
|
||||
"%LocalAppData%\\Riot Games\\Riot Client\\Data\\Sessions": "Sessions",
|
||||
"%LocalAppData%\\Riot Games\\Riot Client\\Data\\RiotClientPrivateSettings.yaml": "RiotClientPrivateSettings.yaml",
|
||||
"%LocalAppData%\\Riot Games\\Riot Client\\Data\\RiotGamesPrivateSettings.yaml": "RiotGamesPrivateSettings.yaml",
|
||||
"%LocalAppData%\\Riot Games\\Riot Client\\Config\\RiotClientSettings.yaml": "RiotClientSettings.yaml",
|
||||
"%LocalAppData%\\Riot Games\\Riot Client\\WattToolkit.id": "WattToolkit.id"
|
||||
},
|
||||
@@ -177,8 +178,7 @@
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\webcache\\common\\Session Storage\\*.log": "ProgramData\\Session Storage\\",
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\webcache\\common\\Session Storage\\*.ldb": "ProgramData\\Session Storage\\",
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\webcache\\common\\Cookies": "ProgramData\\Cookies",
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\storage\\etags.db": "ProgramData\\storage\\etags.db",
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\storage\\galaxy-2.0.db*": "ProgramData\\storage\\",
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\storage\\*": "ProgramData\\storage\\",
|
||||
"%ProgramData%\\GOG.com\\Galaxy\\config.json": "ProgramData\\config.json"
|
||||
},
|
||||
"ProfilePicPath": "%ProgramData%\\GOG.com\\Galaxy\\webcache\\%UniqueId%\\user\\%LARGEST%.jpg",
|
||||
|
||||
@@ -93,8 +93,14 @@ public sealed partial class GameAccountPageViewModel
|
||||
{
|
||||
GamePlatforms?[0].LoadUsers();
|
||||
|
||||
if (!OperatingSystem2.IsWindows())
|
||||
{
|
||||
AddGamePlatforms = [];
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = GetSupportPlatforms();
|
||||
if (temp != null && OperatingSystem2.IsWindows())
|
||||
if (temp != null)
|
||||
{
|
||||
if (GameAccountSettings.EnablePlatforms.Any_Nullable())
|
||||
{
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
</DataTemplate>
|
||||
</MenuItem.DataTemplates>-->
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" />
|
||||
<MenuItem Header="-" IsVisible="{Binding !!AddGamePlatforms.Count}" />
|
||||
<MenuItem Command="{Binding LoginNewCommand}" Header="{Binding Path=Res.UserChange_LoginNewAccount, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<MenuItem.Icon>
|
||||
<ui:SymbolIcon Symbol="Add" />
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsProtection}"
|
||||
Text="{Binding Path=Res.Achievement_IsProtection, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
Text="{Binding Path=Res.Achievement_IsProtection, Mode=OneWay, StringFormat={}({0}), Source={x:Static s:ResourceService.Current}}" />
|
||||
<StackPanel
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
<PackageReference Include="Squid-Box.SevenZipSharp.Lite" />
|
||||
<PackageReference Include="SharpZipLib" />
|
||||
<PackageReference Include="ZstdNet" />
|
||||
<!--<PackageReference Include="BsDiff" />-->
|
||||
<PackageReference Include="BsDiff" />
|
||||
<PackageReference Include="7-Zip.NativeAssets.Win32" />
|
||||
<PackageReference Include="Portable.BouncyCastle" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
||||
|
||||
@@ -1141,7 +1141,7 @@ publish -c {0} -p:OutputType={1} -p:PublishDir=bin\{0}\Publish\win-any -p:Publis
|
||||
yield return AssemblyInfo.Accelerator;
|
||||
yield return AssemblyInfo.GameAccount;
|
||||
yield return AssemblyInfo.GameList;
|
||||
//yield return AssemblyInfo.ArchiSteamFarmPlus;
|
||||
yield return AssemblyInfo.ArchiSteamFarmPlus;
|
||||
yield return AssemblyInfo.Authenticator;
|
||||
if (platform == Platform.Windows)
|
||||
yield return AssemblyInfo.GameTools;
|
||||
|
||||
@@ -1,492 +1,492 @@
|
||||
//using BsDiff;
|
||||
//using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
//using BD.WTTS.Models;
|
||||
using BsDiff;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
using BD.WTTS.Models;
|
||||
|
||||
//namespace BD.WTTS.Client.Tools.Publish.Commands;
|
||||
namespace BD.WTTS.Client.Tools.Publish.Commands;
|
||||
|
||||
//interface IGenerateBsPatch : ICommand
|
||||
//{
|
||||
// const string commandName = "bs";
|
||||
// const string BaseUrl =
|
||||
//#if DEBUG
|
||||
// "https://localhost:9881";
|
||||
interface IGenerateBsPatch : ICommand
|
||||
{
|
||||
const string commandName = "bs";
|
||||
const string BaseUrl =
|
||||
#if DEBUG
|
||||
"https://localhost:9881";
|
||||
|
||||
//#else
|
||||
// "http://192.168.2.16:19001";
|
||||
//#endif
|
||||
#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");
|
||||
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;
|
||||
// }
|
||||
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;
|
||||
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");
|
||||
// }
|
||||
}
|
||||
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;
|
||||
// }
|
||||
/// <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");
|
||||
// }
|
||||
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();
|
||||
/// <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 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);
|
||||
// }
|
||||
// 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);
|
||||
// }
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
// 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>
|
||||
/// 根据 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;
|
||||
// }
|
||||
/// <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);
|
||||
// }
|
||||
// }
|
||||
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>
|
||||
/// 上传版本信息
|
||||
/// </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>
|
||||
/// 传入 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>
|
||||
/// 获取版本号信息
|
||||
/// </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>
|
||||
/// 更具 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?>();
|
||||
/// <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);
|
||||
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($"结束");
|
||||
// }
|
||||
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>
|
||||
/// 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("文件夹不存在");
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
/// <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("文件夹不存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,9 @@ $"""
|
||||
string? pfxFilePath = null,
|
||||
string? workingDirectory = null)
|
||||
{
|
||||
if (pfxFilePath != pfxFilePath_MSStore_CodeSigning)
|
||||
return;
|
||||
|
||||
ProcessStartInfo psi;
|
||||
switch (pfxFilePath)
|
||||
{
|
||||
|
||||
+4
-1
@@ -11,7 +11,10 @@ partial class WindowsPlatformServiceImpl
|
||||
{
|
||||
SystemEvents.SessionEnding += (sender, e) =>
|
||||
{
|
||||
action.Invoke();
|
||||
if (e.Reason == SessionEndReasons.SystemShutdown)
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ public sealed class NoticeService : ReactiveObject
|
||||
{
|
||||
Notices = new ObservableCollection<OfficialMessageItemDTO>();
|
||||
|
||||
_timer = new Timer(async _ =>
|
||||
_timer = new Timer(_ =>
|
||||
{
|
||||
await GetNewsAsync();
|
||||
GetNewsAsync().Wait();
|
||||
}, null, TimeSpan.FromHours(2), TimeSpan.FromHours(2));
|
||||
|
||||
//NoticesSource = new(x => x.Id);
|
||||
@@ -60,7 +60,6 @@ public sealed class NoticeService : ReactiveObject
|
||||
if (item.PushTime > GeneralSettings.LastLookNoticeDateTime.Value)
|
||||
{
|
||||
item.Unread = true;
|
||||
|
||||
UnreadNotificationsCount++;
|
||||
}
|
||||
}
|
||||
@@ -75,6 +74,7 @@ public sealed class NoticeService : ReactiveObject
|
||||
//}
|
||||
var notice = Notices.First(x => x.Unread == true);
|
||||
INotificationService.Instance.Notify(notice.Content, NotificationType.Announcement, title: notice.Title);
|
||||
GeneralSettings.LastLookNoticeDateTime.Value = DateTimeOffset.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public abstract class SettingsPropertyBase
|
||||
|
||||
public abstract void RaiseValueChanged(bool notSave = false);
|
||||
|
||||
public abstract void Reset();
|
||||
public abstract void Reset(bool save = true);
|
||||
|
||||
static readonly Dictionary<Type, List<string>> SettingsProperties = new();
|
||||
static readonly Dictionary<(Type SettingsType, string PropertyName), bool?> SaveNameStatus = new();
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ public class SettingsStructPropertyBase<TValue, [DynamicallyAccessedMembers(Dyna
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
public override void Reset(bool save = true)
|
||||
{
|
||||
var oldValue = value;
|
||||
value = Default; // 赋值当前字段
|
||||
@@ -97,7 +97,7 @@ public class SettingsStructPropertyBase<TValue, [DynamicallyAccessedMembers(Dyna
|
||||
|
||||
OnValueChanged(oldValue, value); // 调用变更事件
|
||||
|
||||
if (AutoSave) // 自动保存
|
||||
if (save && AutoSave) // 自动保存
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public class SettingsProperty<TValue, [DynamicallyAccessedMembers(DynamicallyAcc
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
public override void Reset(bool save = true)
|
||||
{
|
||||
var oldValue = value;
|
||||
value = Default; // 赋值当前字段
|
||||
@@ -144,7 +144,7 @@ public class SettingsProperty<TValue, [DynamicallyAccessedMembers(DynamicallyAcc
|
||||
|
||||
OnValueChanged(oldValue, value); // 调用变更事件
|
||||
|
||||
if (AutoSave) // 自动保存
|
||||
if (save && AutoSave) // 自动保存
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed partial class SettingsPageViewModel : TabItemViewModel
|
||||
{
|
||||
if (string.IsNullOrEmpty(UISettings.StartDefaultPageName.Value))
|
||||
{
|
||||
return s.Id == "Welcome";
|
||||
return s.Id == "Watt Toolkit-Welcome";
|
||||
}
|
||||
return s.Id == UISettings.StartDefaultPageName.Value;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user