Compare commits

...

11 Commits

Author SHA1 Message Date
JieXu
2b28254fbc Update ResUI.fr.resx (#8472) 2025-12-10 17:56:19 +08:00
DHR60
6e27dca6cd Add TLS ALPN check for WS (#8469) 2025-12-09 20:22:13 +08:00
DHR60
7cee98887b Refactor Node Precheck (#8464) 2025-12-09 20:03:07 +08:00
DHR60
3885ff8b31 Fix Shadowsocks Fmt (#8462) 2025-12-08 19:55:27 +08:00
2dust
12abf383e9 up 7.16.6 2025-12-07 15:32:45 +08:00
2dust
5bef02bd6d Code clean 2025-12-07 15:32:03 +08:00
2dust
592f1260b5 Remove Cloudflare IP API URL from IPAPIUrls
https://github.com/2dust/v2rayN/issues/8441
2025-12-07 15:24:54 +08:00
2dust
18303688d7 Refactor AddGroupServerWindow tab controls layout 2025-12-07 15:22:40 +08:00
2dust
5c4b7f6636 Update Directory.Packages.props 2025-12-07 15:22:19 +08:00
tt2563
37cce2fa35 「desktop版本-啟用連線資訊測試位址自訂輸入」 (#8456) 2025-12-07 15:21:11 +08:00
dependabot[bot]
6f8b65c75b Bump actions/checkout from 6.0.0 to 6.0.1 (#8437)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-04 18:24:14 +08:00
23 changed files with 246 additions and 153 deletions

View File

@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'
@@ -110,7 +110,7 @@ jobs:
dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core rsync findutils tar gzip unzip which
- name: Checkout repo (for scripts)
uses: actions/checkout@v6.0.0
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'

View File

@@ -26,7 +26,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'

View File

@@ -26,7 +26,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'

View File

@@ -27,7 +27,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v6.0.1
- name: Setup
uses: actions/setup-dotnet@v5.0.1

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.16.5</Version>
<Version>7.16.6</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -12,13 +12,13 @@
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageVersion Include="CliWrap" Version="3.10.0" />
<PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.2.1" />
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.2.1" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.1" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.1" />

View File

@@ -586,7 +586,6 @@ public class Global
public static readonly List<string> IPAPIUrls =
[
@"https://speed.cloudflare.com/meta",
@"https://api.ip.sb/geoip",
@"https://api-ipv4.ip.sb/geoip",
@"https://api-ipv6.ip.sb/geoip",

View File

@@ -57,7 +57,10 @@ public class ShadowsocksFmt : BaseFmt
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={item.RequestHost};";
pluginArgs += $"path={item.Path};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
else if (item.Network == nameof(ETransport.quic))
{
@@ -75,8 +78,6 @@ public class ShadowsocksFmt : BaseFmt
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
base64Content = base64Content.Replace("=", "\\=");
pluginArgs += $"certRaw={base64Content};";
@@ -85,6 +86,7 @@ public class ShadowsocksFmt : BaseFmt
if (pluginArgs.Length > 0)
{
plugin = "v2ray-plugin";
pluginArgs += "mux=0;";
}
}
@@ -222,6 +224,7 @@ public class ShadowsocksFmt : BaseFmt
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
var hasTls = pluginParts.Any(t => t == "tls");
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));
var modeValue = mode.Replace("mode=", "");
if (modeValue == "websocket")
@@ -234,7 +237,9 @@ public class ShadowsocksFmt : BaseFmt
}
if (!path.IsNullOrEmpty())
{
item.Path = path.Replace("path=", "");
var pathValue = path.Replace("path=", "");
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
item.Path = pathValue;
}
}
else if (modeValue == "quic")
@@ -258,6 +263,16 @@ public class ShadowsocksFmt : BaseFmt
item.Cert = certPem;
}
}
if (!mux.IsNullOrEmpty())
{
var muxValue = mux.Replace("mux=", "");
var muxCount = muxValue.ToInt();
if (muxCount > 0)
{
return null;
}
}
}
}

View File

@@ -3,17 +3,17 @@ namespace ServiceLib.Manager;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// </summary>
public class ActionPrecheckManager(Config config)
public class ActionPrecheckManager
{
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
private static readonly Lazy<ActionPrecheckManager> _instance = new();
public static ActionPrecheckManager Instance => _instance.Value;
private readonly Config _config = config;
// sing-box supported transports for different protocol types
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
private static readonly HashSet<EConfigType> SingboxTransportSupportedProtocols =
[EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks];
private static readonly HashSet<string> SingboxShadowsocksAllowedTransports =
[nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)];
@@ -54,6 +54,7 @@ public class ActionPrecheckManager(Config config)
{
return [];
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType);
}
@@ -69,115 +70,35 @@ public class ActionPrecheckManager(Config config)
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
return errors;
}
if (!item.IsComplex())
else if (item.ConfigType.IsGroupType())
{
if (item.Address.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
if (item.Port is <= 0 or >= 65536)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
if (errors.Count > 0)
{
return errors;
}
var groupErrors = await ValidateGroupNode(item, coreType);
errors.AddRange(groupErrors);
return errors;
}
else if (!item.IsComplex())
{
var normalErrors = await ValidateNormalNode(item, coreType);
errors.AddRange(normalErrors);
return errors;
}
if (item.ConfigType.IsGroupType())
return errors;
}
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
if (item.Address.IsNullOrEmpty())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors);
}
if (item.Port is <= 0 or > 65535)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
@@ -189,20 +110,151 @@ public class ActionPrecheckManager(Config config)
if (transportError != null)
{
errors.Add(transportError);
return errors;
}
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.Xray), item.ConfigType.ToString()));
}
}
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
if (item.StreamSecurity == Global.StreamSecurity)
{
// check certificate validity
if ((!item.Cert.IsNullOrEmpty()) && (CertPemManager.ParsePemChain(item.Cert).Count == 0))
{
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
}
}
if (item.StreamSecurity == Global.StreamSecurityReality)
{
if (item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
}
if (item.Network == nameof(ETransport.xhttp)
&& !item.Extra.IsNullOrEmpty())
{
// check xhttp extra json validity
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
if (xhttpExtra is null)
{
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
}
}
// ws with tls, tls alpn should contain "http/1.1" in xray core
// rfc6455
// https://github.com/XTLS/Xray-core/blob/81f8f398c7b2b845853b1e85087c6122acc6db0b/transport/internet/tls/tls.go#L95-L116
if (item.Network == nameof(ETransport.ws)
&& item.StreamSecurity == Global.StreamSecurity)
{
var alpnList = Utils.String2List(item.Alpn) ?? [];
if (alpnList.Count > 0 && !alpnList.Contains("http/1.1"))
{
errors.Add(ResUI.AlpnMustContainHttp11ForWsTls);
}
}
return errors;
}
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
}
return errors;
}
@@ -269,7 +321,7 @@ public class ActionPrecheckManager(Config config)
if (node is not null)
{
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s));
}
else if (tag.IsNotEmpty())
{
@@ -287,7 +339,7 @@ public class ActionPrecheckManager(Config config)
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config);
if (routing == null)
{
return errors;
@@ -315,7 +367,7 @@ public class ActionPrecheckManager(Config config)
}
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s));
}
return errors;

View File

@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ResUI {
@@ -78,6 +78,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 ALPN must contain &apos;http/1.1&apos; when using WebSocket with TLS. 的本地化字符串。
/// </summary>
public static string AlpnMustContainHttp11ForWsTls {
get {
return ResourceManager.GetString("AlpnMustContainHttp11ForWsTls", resourceCulture);
}
}
/// <summary>
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。
/// </summary>

View File

@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root>

View File

@@ -1636,6 +1636,9 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<value>Afficher dans le Dock de macOS (redém. requis)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</value>
</data>
</root>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>Avec WebSocket et TLS, lALPN doit inclure http/1.1.</value>
</data>
</root>

View File

@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root>

View File

@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root>

View File

@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root>

View File

@@ -1638,4 +1638,7 @@
<data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value>
</data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>使用 WebSocket+TLS 时ALPN 必须包含 'http/1.1'。</value>
</data>
</root>

View File

@@ -1638,4 +1638,7 @@
<data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value>
</data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root>

View File

@@ -46,7 +46,10 @@ public partial class CoreConfigSingboxService
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={node.RequestHost};";
pluginArgs += $"path={node.Path};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
else if (node.Network == nameof(ETransport.quic))
{
@@ -64,8 +67,6 @@ public partial class CoreConfigSingboxService
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
base64Content = base64Content.Replace("=", "\\=");
pluginArgs += $"certRaw={base64Content};";
@@ -74,6 +75,9 @@ public partial class CoreConfigSingboxService
if (pluginArgs.Length > 0)
{
outbound.plugin = "v2ray-plugin";
pluginArgs += "mux=0;";
// pluginStr remove last ';'
pluginArgs = pluginArgs[..^1];
outbound.plugin_opts = pluginArgs;
}
}
@@ -365,7 +369,7 @@ public partial class CoreConfigSingboxService
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
var wsPath = node.Path;
// Parse eh and ed parameters from path using regex
if (!wsPath.IsNullOrEmpty())
{

View File

@@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia;
using Avalonia.Media;
namespace v2rayN.Desktop.Common;
public static class AppBuilderExtension
@@ -11,7 +5,7 @@ public static class AppBuilderExtension
public static AppBuilder WithFontByDefault(this AppBuilder appBuilder)
{
var fallbacks = new List<FontFallback>();
var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"));
fallbacks.Add(new FontFallback { FontFamily = notoSansSc });

View File

@@ -34,7 +34,6 @@
</StackPanel>
<Grid
Grid.Row="0"
ColumnDefinitions="300,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -89,7 +88,7 @@
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabControl HorizontalContentAlignment="Stretch" DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid
Margin="{StaticResource Margin8}"
@@ -131,9 +130,7 @@
VerticalAlignment="Center" />
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
<DataGrid
x:Name="lstChild"

View File

@@ -592,7 +592,8 @@
Grid.Row="20"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
IsEditable="True" />
<TextBlock
Grid.Row="21"

View File

@@ -101,7 +101,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);

View File

@@ -134,7 +134,10 @@
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabControl
Margin="{StaticResource Margin8}"
HorizontalContentAlignment="Left"
DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
@@ -188,9 +191,7 @@
Style="{StaticResource DefTextBox}" />
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
<DataGrid
x:Name="lstChild"