mirror of
https://github.com/2dust/v2rayN.git
synced 2026-07-26 18:02:05 +03:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae7d54c2e5 | ||
|
|
56d0d65b06 | ||
|
|
5e8e189c27 | ||
|
|
3fee86d44a | ||
|
|
dd77eb79c6 | ||
|
|
d26a2559a6 | ||
|
|
e5ba1759aa | ||
|
|
bfdee37cc1 | ||
|
|
cf89cfcd95 | ||
|
|
39a988c704 | ||
|
|
2b28254fbc | ||
|
|
6e27dca6cd | ||
|
|
7cee98887b | ||
|
|
3885ff8b31 | ||
|
|
12abf383e9 | ||
|
|
5bef02bd6d | ||
|
|
592f1260b5 | ||
|
|
18303688d7 | ||
|
|
5c4b7f6636 | ||
|
|
37cce2fa35 | ||
|
|
6f8b65c75b |
10
.github/workflows/build-linux.yml
vendored
10
.github/workflows/build-linux.yml
vendored
@@ -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'
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: v2rayN-linux
|
||||
path: |
|
||||
@@ -110,13 +110,13 @@ 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'
|
||||
|
||||
- name: Restore build artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: v2rayN-linux
|
||||
path: ${{ github.workspace }}/v2rayN/Release
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||
|
||||
- name: Upload RPM artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: v2rayN-rpm
|
||||
path: dist/rpm/**/*.rpm
|
||||
|
||||
4
.github/workflows/build-osx.yml
vendored
4
.github/workflows/build-osx.yml
vendored
@@ -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'
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: v2rayN-macos
|
||||
path: |
|
||||
|
||||
4
.github/workflows/build-windows-desktop.yml
vendored
4
.github/workflows/build-windows-desktop.yml
vendored
@@ -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'
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: v2rayN-windows-desktop
|
||||
path: |
|
||||
|
||||
4
.github/workflows/build-windows.yml
vendored
4
.github/workflows/build-windows.yml
vendored
@@ -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
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: v2rayN-windows
|
||||
path: |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.16.5</Version>
|
||||
<Version>7.16.8</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.9" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.9" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.9" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.10" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.10" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.10" />
|
||||
<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="MessageBox.Avalonia" Version="3.3.1" />
|
||||
<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" />
|
||||
<PackageVersion Include="NLog" Version="6.0.6" />
|
||||
<PackageVersion Include="NLog" Version="6.0.7" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
11
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -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 'http/1.1' when using WebSocket with TLS. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string AlpnMustContainHttp11ForWsTls {
|
||||
get {
|
||||
return ResourceManager.GetString("AlpnMustContainHttp11ForWsTls", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -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>
|
||||
@@ -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, l’ALPN doit inclure ‘http/1.1’.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -291,13 +291,6 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
||||
return url;
|
||||
}
|
||||
|
||||
//Check for standalone windows .Net version
|
||||
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll"))
|
||||
&& File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll")))
|
||||
{
|
||||
return url?.Replace(".zip", "-SelfContained.zip");
|
||||
}
|
||||
|
||||
//Check for avalonia desktop windows version
|
||||
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
|
||||
{
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Title="v2rayN"
|
||||
Width="1200"
|
||||
Height="800"
|
||||
MinWidth="900"
|
||||
MinWidth="600"
|
||||
x:DataType="vms:MainWindowViewModel"
|
||||
Icon="/Assets/NotifyIcon1.ico"
|
||||
ShowInTaskbar="True"
|
||||
|
||||
@@ -592,7 +592,8 @@
|
||||
Grid.Row="20"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Margin="{StaticResource Margin4}"
|
||||
IsEditable="True" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -40,6 +40,14 @@
|
||||
<DataGrid.KeyBindings>
|
||||
<KeyBinding Command="{Binding SubDeleteCmd}" Gesture="Delete" />
|
||||
</DataGrid.KeyBindings>
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem x:Name="menuSubAdd2" Header="{x:Static resx:ResUI.menuSubAdd}" />
|
||||
<MenuItem x:Name="menuSubDelete2" Header="{x:Static resx:ResUI.menuSubDelete}" />
|
||||
<MenuItem x:Name="menuSubEdit2" Header="{x:Static resx:ResUI.menuSubEdit}" />
|
||||
<MenuItem x:Name="menuSubShare2" Header="{x:Static resx:ResUI.menuSubShare}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
|
||||
@@ -29,6 +29,11 @@ public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
|
||||
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd2).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete2).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit2).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare2).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
SecondaryColor="Lime" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<system:Double x:Key="MenuItemHeight">26</system:Double>
|
||||
<system:Double x:Key="MenuItemHeight">32</system:Double>
|
||||
<system:Double x:Key="StdFontSize">12</system:Double>
|
||||
<system:Double x:Key="StdFontSize1">13</system:Double>
|
||||
<system:Double x:Key="StdFontSize-1">11</system:Double>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
Title="v2rayN"
|
||||
Width="1200"
|
||||
Height="800"
|
||||
MinWidth="900"
|
||||
MinWidth="800"
|
||||
x:TypeArguments="vms:MainWindowViewModel"
|
||||
Icon="/Resources/v2rayN.ico"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
|
||||
@@ -93,6 +93,26 @@
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource DefDataGrid}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem
|
||||
x:Name="menuSubAdd2"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSubAdd}" />
|
||||
<MenuItem
|
||||
x:Name="menuSubDelete2"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSubDelete}" />
|
||||
<MenuItem
|
||||
x:Name="menuSubEdit2"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSubEdit}" />
|
||||
<MenuItem
|
||||
x:Name="menuSubShare2"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSubShare}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
|
||||
@@ -25,6 +25,11 @@ public partial class SubSettingWindow
|
||||
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd2).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete2).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit2).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare2).DisposeWith(disposables);
|
||||
});
|
||||
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user