Compare commits

...

16 Commits

Author SHA1 Message Date
2dust eee43288a4 up 7.21.3 2026-05-10 17:58:35 +08:00
2dust 61ff871dd2 Update GlobalHotKeys 2026-05-10 17:54:50 +08:00
DHR60 f8bc706cda Fix (#9271)
* Fix

* Fix res and uri

* Fix core config
2026-05-10 14:21:51 +08:00
2dust e7973840ce up 7.21.2 2026-05-08 20:11:01 +08:00
VinnyTheFemboy 212071681d Fix bind interface handling in desktop and sing-box (#9258)
* Fix desktop bind interface setting

* Fix sing-box bind interface config

* Update CoreConfigSingboxServiceTests.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-05-08 19:34:25 +08:00
tt2563 0f9bfeb275 Update Traditional Chinese translation (#9253)
* Update traditional Chinese translations in ResUI

 Update Traditional Chinese translation

* Update binding interface tip for clarity
2026-05-08 19:13:05 +08:00
VinnyTheFemboy f5059f1165 Fix sing-box TUN custom config inbound (#9259) 2026-05-08 19:12:09 +08:00
DHR60 2dc967bc04 Fix (#9251) 2026-05-07 10:11:41 +08:00
DHR60 75ea81dd69 Inner uri (#9245)
* Inner uri import and export

* Add tests

* Fix

* Compress export length
2026-05-06 20:33:35 +08:00
DHR60 d13f7a4db6 Fix json compact (#9246) 2026-05-06 15:36:26 +08:00
DHR60 f073b14fcc Add PR test (#9244)
* Add pr test

* Fix
2026-05-06 15:35:53 +08:00
DHR60 1a44af33d0 Wireguard (#9241)
* Adjust mtu

* Add Wireguard PresharedKey

* WireGuard config import

* AI opt

* Opt
2026-05-06 15:35:10 +08:00
DHR60 8450f2e420 Add bind interface (#9222) 2026-05-04 14:38:32 +08:00
DHR60 37ef25cbfe Fix (#9235)
* Perf routing

* GeoPrefix move to Global

* Fix negative rules

* Fix
2026-05-03 16:09:47 +08:00
DHR60 0fac18ba95 Fix (#9230) 2026-05-02 19:01:51 +08:00
DHR60 3ccd59d1dc Fix (#9224) 2026-05-02 19:01:31 +08:00
51 changed files with 1355 additions and 255 deletions
+29
View File
@@ -0,0 +1,29 @@
name: Code Test
on:
pull_request:
branches:
- master
paths:
- 'v2rayN/ServiceLib/Services/CoreConfig/**'
- 'v2rayN/ServiceLib/Handler/Fmt/**'
- '.github/workflows/test.yml'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: '8.0.x'
- name: Test Code
working-directory: ./v2rayN
run: dotnet test ./ServiceLib.Tests
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.21.1</Version>
<Version>7.21.3</Version>
</PropertyGroup>
<PropertyGroup>
@@ -1,6 +1,7 @@
using AwesomeAssertions;
using ServiceLib.Common;
using ServiceLib.Enums;
using ServiceLib.Manager;
using ServiceLib.Models;
using ServiceLib.Services.CoreConfig;
using Xunit;
@@ -28,6 +29,54 @@ public class CoreConfigSingboxServiceTests
singboxConfig.inbounds.Should().Contain(i => i.type == nameof(EInboundProtocol.mixed));
}
[Fact]
public void GenerateClientConfigContent_TunWithLoopbackPreSocks_ShouldKeepMixedInbound()
{
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateSocksNode(ECoreType.sing_box);
node.Address = Global.Loopback;
node.Port = 1080;
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
{
IsTunEnabled = true,
};
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
cfg.inbounds.Should().Contain(i =>
i.type == nameof(EInboundProtocol.mixed)
&& i.listen == Global.Loopback
&& i.listen_port == AppManager.Instance.GetLocalPort(EInboundProtocol.socks));
cfg.inbounds.Should().Contain(i => i.type == "tun");
}
[Fact]
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
{
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
config.CoreBasicItem.BindInterface = "eth0";
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
{
IsTunEnabled = true,
};
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
proxy.bind_interface.Should().Be("eth0");
proxy.detour.Should().BeNullOrEmpty();
}
[Fact]
public void GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildSelector()
{
@@ -0,0 +1,37 @@
using AwesomeAssertions;
using ServiceLib.Enums;
using ServiceLib.Handler.Fmt;
using ServiceLib.Tests.CoreConfig;
using Xunit;
namespace ServiceLib.Tests.Fmt;
public class InnerFmtTests
{
[Fact]
public void ToUriAndResolve_ShouldRoundTripPolicyGroupReferences()
{
var childA = CoreConfigTestFactory.CreateSocksNode(ECoreType.Xray, "child-a", "child-a");
var childB = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "child-b", "child-b");
var group = CoreConfigTestFactory.CreatePolicyGroupNode(ECoreType.Xray, "group-1", "group-1",
[childA.IndexId, childB.IndexId]);
group.SetProtocolExtra(group.GetProtocolExtra() with { SubChildItems = "original-sub" });
var uri = InnerFmt.ToUri([group, childA, childB]);
uri.Should().NotBeNullOrWhiteSpace();
var resolved = InnerFmt.Resolve(uri!, "sub-123");
resolved.Should().NotBeNull();
resolved.Should().HaveCount(3);
var resolvedGroup = resolved!.Single(x => x.Remarks == group.Remarks);
var resolvedChildA = resolved.Single(x => x.Remarks == childA.Remarks);
var resolvedChildB = resolved.Single(x => x.Remarks == childB.Remarks);
resolvedGroup.ConfigType.Should().Be(EConfigType.PolicyGroup);
resolvedGroup.GetProtocolExtra().SubChildItems.Should().Be("sub-123");
resolvedGroup.GetProtocolExtra().ChildItems.Should().Be($"{resolvedChildA.IndexId},{resolvedChildB.IndexId}");
}
}
@@ -0,0 +1,47 @@
using AwesomeAssertions;
using ServiceLib.Handler.Fmt;
using Xunit;
namespace ServiceLib.Tests.Fmt;
public class WireguardFmtTests
{
[Fact]
public void ResolveConfig_ShouldParsePeersAndIgnoreInlineComments()
{
const string config =
"""
[Interface]
PrivateKey = interface-private-key
Address = 10.0.0.2/32, fd00::2/128 ; inline comment
MTU = 1420
[Peer]
PublicKey = peer-public-key
PresharedKey = peer-preshared-key
Reserved = 1, 2, 3 # inline comment
Endpoint = [2001:db8::1]:51820 # inline comment
[Peer]
PublicKey = peer-public-key-2
Endpoint = example.com:12345
""";
var resolved = WireguardFmt.ResolveConfig(config);
resolved.Should().NotBeNull();
resolved.Should().HaveCount(2);
var first = resolved![0];
first.Address.Should().Be("2001:db8::1");
first.Port.Should().Be(51820);
first.Password.Should().Be("interface-private-key");
first.GetProtocolExtra().WgReserved.Should().Be("1, 2, 3");
first.GetProtocolExtra().WgInterfaceAddress.Should().Be("10.0.0.2/32, fd00::2/128");
first.GetProtocolExtra().WgMtu.Should().Be(1420);
var second = resolved[1];
second.Address.Should().Be("example.com");
second.Port.Should().Be(12345);
}
}
+21 -1
View File
@@ -17,6 +17,13 @@ public class JsonUtils
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonSerializerOptions _defaultSerializeNoIndentedOptions = new()
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
{
WriteIndented = true,
@@ -24,6 +31,13 @@ public class JsonUtils
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonSerializerOptions _nullValueSerializeNoIndentedOptions = new()
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip
@@ -104,7 +118,13 @@ public class JsonUtils
{
return result;
}
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
var options = (nullValue, indented) switch
{
(true, true) => _nullValueSerializeOptions,
(true, false) => _nullValueSerializeNoIndentedOptions,
(false, true) => _defaultSerializeOptions,
_ => _defaultSerializeNoIndentedOptions
};
result = JsonSerializer.Serialize(obj, options);
}
catch (Exception ex)
+3
View File
@@ -64,9 +64,12 @@ public class Global
public const string HttpsProtocol = "https://";
public const string SocksProtocol = "socks://";
public const string Socks5Protocol = "socks5://";
public const string InnerUriProtocol = "v2rayn://";
public const string AsIs = "AsIs";
public const string IPIfNonMatch = "IPIfNonMatch";
public const string IPOnDemand = "IPOnDemand";
public const string GeoSitePrefix = "geosite:";
public const string GeoIPPrefix = "geoip:";
public const string UserEMail = "t@t.tt";
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
@@ -47,6 +47,8 @@ public class CoreConfigContextBuilder
ProtectDomainList = [],
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
IsWindows = Utils.IsWindows(),
IsMacOS = Utils.IsMacOS(),
};
var validatorResult = NodeValidatorResult.Empty();
var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node);
+130 -35
View File
@@ -41,7 +41,6 @@ public static class ConfigHandler
Loglevel = "warning",
MuxEnabled = false,
};
config.CoreBasicItem.SendThrough = config.CoreBasicItem.SendThrough?.TrimEx();
if (config.Inbound == null)
{
@@ -705,10 +704,12 @@ public static class ConfigHandler
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Hysteria2;
//profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Fingerprint = string.Empty;
profileItem.Alpn = string.Empty;
//profileItem.Alpn = "h3";
profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty())
@@ -748,6 +749,7 @@ public static class ConfigHandler
profileItem.Username = profileItem.Username.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Network = string.Empty;
profileItem.Fingerprint = string.Empty;
var congestionControl = profileItem.GetProtocolExtra().CongestionControl;
if (!Global.TuicCongestionControls.Contains(congestionControl))
@@ -788,12 +790,30 @@ public static class ConfigHandler
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
var wgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx();
if (!wgReserved.IsNullOrEmpty()
&& !wgReserved.Contains(','))
{
// Base64 format, convert to standard format
try
{
var bytes = Convert.FromBase64String(wgReserved);
var reserved = new byte[3];
Array.Copy(bytes, reserved, Math.Min(bytes.Length, 3));
wgReserved = string.Join(", ", reserved);
}
catch
{
// If conversion fails, keep the original value
}
}
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
WgPublicKey = profileItem.GetProtocolExtra().WgPublicKey?.TrimEx(),
WgPresharedKey = profileItem.GetProtocolExtra().WgPresharedKey?.TrimEx(),
WgInterfaceAddress = profileItem.GetProtocolExtra().WgInterfaceAddress?.TrimEx(),
WgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx(),
WgReserved = wgReserved,
WgMtu = profileItem.GetProtocolExtra().WgMtu is null or <= 0 ? Global.TunMtus.First() : profileItem.GetProtocolExtra().WgMtu,
});
@@ -851,8 +871,10 @@ public static class ConfigHandler
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Username = profileItem.Username.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Fingerprint = string.Empty;
profileItem.Alpn = string.Empty;
profileItem.Network = string.Empty;
profileItem.AllowInsecure = "false";
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
@@ -1506,10 +1528,8 @@ public static class ConfigHandler
}
var subFilter = string.Empty;
//remove sub items
if (isSub && subid.IsNotEmpty())
{
await RemoveServersViaSubid(config, subid, isSub);
subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? "";
}
@@ -1612,10 +1632,6 @@ public static class ConfigHandler
}
if (lstProfiles != null && lstProfiles.Count > 0)
{
if (isSub && subid.IsNotEmpty())
{
await RemoveServersViaSubid(config, subid, isSub);
}
var count = 0;
foreach (var it in lstProfiles)
{
@@ -1635,40 +1651,23 @@ public static class ConfigHandler
ProfileItem? profileItem = null;
//Is sing-box configuration
if (profileItem is null)
{
profileItem = SingboxFmt.ResolveFull(strData, subRemarks);
}
profileItem ??= SingboxFmt.ResolveFull(strData, subRemarks);
//Is v2ray configuration
if (profileItem is null)
{
profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
}
profileItem ??= V2rayFmt.ResolveFull(strData, subRemarks);
//Is Html Page
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
{
return -1;
}
//Is Clash configuration
if (profileItem is null)
{
profileItem = ClashFmt.ResolveFull(strData, subRemarks);
}
profileItem ??= ClashFmt.ResolveFull(strData, subRemarks);
//Is hysteria configuration
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks);
}
profileItem ??= Hysteria2Fmt.ResolveFull2(strData, subRemarks);
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
{
return -1;
}
if (isSub && subid.IsNotEmpty())
{
await RemoveServersViaSubid(config, subid, isSub);
}
profileItem.Subid = subid;
profileItem.IsSub = isSub;
profileItem.PreSocksPort = preSocksPort;
@@ -1698,11 +1697,6 @@ public static class ConfigHandler
return -1;
}
if (isSub && subid.IsNotEmpty())
{
await RemoveServersViaSubid(config, subid, isSub);
}
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
if (lstSsServer?.Count > 0)
{
@@ -1723,6 +1717,86 @@ public static class ConfigHandler
return -1;
}
private static async Task<int> AddBatchServers4Wireguard(Config config, string strData, string subid, bool isSub)
{
if (strData.IsNullOrEmpty())
{
return -1;
}
if (!(strData.Contains("[Interface]", StringComparison.OrdinalIgnoreCase)
&& strData.Contains("[Peer]", StringComparison.OrdinalIgnoreCase)))
{
return -1;
}
var lstServer = WireguardFmt.ResolveConfig(strData);
if (lstServer?.Count > 0)
{
var counter = 0;
foreach (var item in lstServer)
{
item.Subid = subid;
item.IsSub = isSub;
if (await AddWireguardServer(config, item) == 0)
{
counter++;
}
}
await SaveConfig(config);
return counter;
}
return -1;
}
private static async Task<int> AddBatchServers4InnerUri(Config config, string strData, string subid, bool isSub)
{
if (strData.IsNullOrEmpty())
{
return -1;
}
var lstServer = InnerFmt.Resolve(strData, subid);
if (lstServer?.Count > 0)
{
var counter = 0;
List<ProfileItem> lstAdd = [];
foreach (var profileItem in lstServer)
{
profileItem.Subid = subid;
profileItem.IsSub = isSub;
var addStatus = profileItem.ConfigType switch
{
EConfigType.VMess => await AddVMessServer(config, profileItem, false),
EConfigType.Shadowsocks => await AddShadowsocksServer(config, profileItem, false),
EConfigType.HTTP => await AddHttpServer(config, profileItem, false),
EConfigType.SOCKS => await AddSocksServer(config, profileItem, false),
EConfigType.Trojan => await AddTrojanServer(config, profileItem, false),
EConfigType.VLESS => await AddVlessServer(config, profileItem, false),
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
EConfigType.PolicyGroup or EConfigType.ProxyChain => await AddServerCommon(config, profileItem, false),
_ => -1,
};
if (addStatus == 0)
{
counter++;
lstAdd.Add(profileItem);
}
}
if (lstAdd.Count > 0)
{
await SQLiteHelper.Instance.InsertAllAsync(lstAdd);
}
await SaveConfig(config);
return counter;
}
return -1;
}
/// <summary>
/// Main entry point for adding batch servers from various formats
/// Tries different parsing methods to import as many servers as possible
@@ -1744,6 +1818,7 @@ public static class ConfigHandler
{
lstOriSub = await AppManager.Instance.ProfileItems(subid);
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
await RemoveServersViaSubid(config, subid, true);
}
var counter = 0;
@@ -1765,6 +1840,26 @@ public static class ConfigHandler
counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub);
}
//maybe wireguard config
if (counter < 1)
{
counter = await AddBatchServers4Wireguard(config, strData, subid, isSub);
}
//May be standard uri mixed with internal uri
var innerUriCount = await AddBatchServers4InnerUri(config, strData, subid, isSub);
if (innerUriCount > 0)
{
if (counter > 0)
{
counter += innerUriCount;
}
else
{
counter = innerUriCount;
}
}
//maybe other sub
if (counter < 1)
{
+302
View File
@@ -0,0 +1,302 @@
namespace ServiceLib.Handler.Fmt;
public class InnerFmt
{
private static readonly Lazy<string> SessionSalt = new(() => Utils.GetGuid(false));
public static List<ProfileItem>? Resolve(string strData, string subid)
{
var list = new List<ProfileItem>();
// Overwrite externally imported indexIds to avoid possible sources of attacks
var indexIdMap = new Dictionary<string, string>();
using (var reader = new StringReader(strData))
{
while (reader.ReadLine() is { } line)
{
if (line.IsNullOrEmpty())
{
continue;
}
var trimmedLine = line.Trim();
if (!trimmedLine.StartsWith(Global.InnerUriProtocol, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var profileItem = ResolveSingle(trimmedLine);
if (profileItem is null)
{
continue;
}
if (profileItem.ConfigType == EConfigType.Custom)
{
// Unsupported, also to avoid possible sources of attacks, skip it
continue;
}
// overwrite indexId
var newIndexId = Utils.GetGuid(false);
if (!profileItem.IndexId.IsNullOrEmpty())
{
// Ignore duplicated indexId
indexIdMap[profileItem.IndexId] = newIndexId;
}
profileItem.IndexId = newIndexId;
list.Add(profileItem);
}
}
// For group-type profile items, also overwrite the ChildItems and ChildSubId
var emptyGroupProfileList = new List<ProfileItem>();
foreach (var item in list.Where(i => i.ConfigType.IsGroupType()))
{
var protocolExtra = item.GetProtocolExtra();
// Only allow "self" as a special value for SubChildItems to avoid possible sources of attacks,
// which means it will be replaced with the subid, otherwise set it to null
//if (!protocolExtra.SubChildItems.IsNullOrEmpty())
if (protocolExtra.SubChildItems == "self")
{
protocolExtra = protocolExtra with
{
SubChildItems = subid
};
}
else
{
protocolExtra = protocolExtra with
{
SubChildItems = null
};
}
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
{
var newChildIndexIds = childIndexIds
.Select(id => indexIdMap.GetValueOrDefault(id, null))
.Where(id => !id.IsNullOrEmpty())
.ToList();
protocolExtra = protocolExtra with
{
ChildItems = Utils.List2String(newChildIndexIds)
};
}
else
{
protocolExtra = protocolExtra with
{
ChildItems = null
};
}
item.SetProtocolExtra(protocolExtra);
if (protocolExtra.SubChildItems.IsNullOrEmpty()
&& protocolExtra.ChildItems.IsNullOrEmpty())
{
emptyGroupProfileList.Add(item);
}
}
// Remove empty group profile items
list.RemoveAll(emptyGroupProfileList.Contains);
return list;
}
public static string? ToUri(List<ProfileItem> items)
{
var sb = new StringBuilder();
foreach (var item in items)
{
if (item.ConfigType == EConfigType.Custom)
{
continue;
}
var itemClone = JsonUtils.DeepCopy(item);
if (itemClone is null)
{
continue;
}
// overwrite indexId
var originalIndexId = itemClone.IndexId;
var newIndexId = GetReproducibleExportId(originalIndexId);
itemClone.IndexId = newIndexId;
if (itemClone.ConfigType.IsGroupType())
{
var protocolExtra = itemClone.GetProtocolExtra();
if (!protocolExtra.SubChildItems.IsNullOrEmpty())
{
protocolExtra = protocolExtra with
{
SubChildItems = "self"
};
}
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
{
var newChildIndexIds = childIndexIds
.Select(GetReproducibleExportId)
.Where(id => !id.IsNullOrEmpty())
.ToList();
protocolExtra = protocolExtra with
{
ChildItems = Utils.List2String(newChildIndexIds)
};
}
itemClone.SetProtocolExtra(protocolExtra);
}
var uri = ToUriSingle(itemClone);
if (!uri.IsNullOrEmpty())
{
sb.AppendLine(uri);
}
}
return sb.Length > 0 ? sb.ToString() : null;
}
private static ProfileItem? ResolveSingle(string str)
{
// format: v2rayn://vless/{url-safe base64 encoded_string}
var parsedUri = Utils.TryUri(str);
if (parsedUri is null)
{
return null;
}
var segment = parsedUri.AbsolutePath.TrimStart('/');
var decodedResult = Utils.Base64Decode(segment);
var jsonNode = JsonUtils.ParseJson(decodedResult);
if (jsonNode is not JsonObject jsonObj)
{
return null;
}
// flatten
// move jsonObj.ProtoExtraObj to jsonObj.ProtoExtra (string)
// move jsonObj.TransportExtraObj to jsonObj.TransportExtra (string)
if (jsonObj.TryGetPropertyValue("ProtoExtraObj", out var protoExtraNode)
&& protoExtraNode is JsonObject protoExtraObj)
{
jsonObj["ProtoExtra"] = JsonUtils.Serialize(protoExtraObj, false);
jsonObj.Remove("ProtoExtraObj");
}
if (jsonObj.TryGetPropertyValue("TransportExtraObj", out var transportExtraNode)
&& transportExtraNode is JsonObject transportExtraObj)
{
jsonObj["TransportExtra"] = JsonUtils.Serialize(transportExtraObj, false);
jsonObj.Remove("TransportExtraObj");
}
var profileItem = JsonUtils.Deserialize<ProfileItem>(JsonUtils.Serialize(jsonObj, false));
if (profileItem is null)
{
return null;
}
if (profileItem.ConfigVersion != 4)
{
return null;
}
// Check Enum.IsDefined
if (!Enum.IsDefined(typeof(EConfigType), profileItem.ConfigType))
{
return null;
}
if (profileItem.CoreType is not (null or ECoreType.Xray or ECoreType.sing_box))
{
return null;
}
var protocolExtra = profileItem.GetProtocolExtra();
var multipleLoad = protocolExtra.MultipleLoad;
if (multipleLoad is not null && !Enum.IsDefined(typeof(EMultipleLoad), multipleLoad))
{
return null;
}
return profileItem;
}
private static string? ToUriSingle(ProfileItem item)
{
var jsonNode = JsonUtils.ParseJson(JsonUtils.Serialize(item, false));
if (jsonNode is not JsonObject jsonObj)
{
return null;
}
// unflatten
// move jsonObj.ProtoExtra (string) to jsonObj.ProtoExtraObj
// move jsonObj.TransportExtra (string) to jsonObj.TransportExtraObj
if (jsonObj.TryGetPropertyValue("ProtoExtra", out var protoExtraNode)
&& protoExtraNode is JsonValue protoExtraValue
&& protoExtraValue.TryGetValue<string>(out var protoExtraStr)
&& !protoExtraStr.IsNullOrEmpty()
&& JsonUtils.ParseJson(protoExtraStr) is JsonObject protoExtraObj)
{
jsonObj["ProtoExtraObj"] = protoExtraObj;
jsonObj.Remove("ProtoExtra");
}
if (jsonObj.TryGetPropertyValue("TransportExtra", out var transportExtraNode)
&& transportExtraNode is JsonValue transportExtraValue
&& transportExtraValue.TryGetValue<string>(out var transportExtraStr)
&& !transportExtraStr.IsNullOrEmpty()
&& JsonUtils.ParseJson(transportExtraStr) is JsonObject transportExtraObj)
{
jsonObj["TransportExtraObj"] = transportExtraObj;
jsonObj.Remove("TransportExtra");
}
// remove subid and isSub
jsonObj.Remove("Subid");
jsonObj.Remove("IsSub");
// Remove empty properties to reduce the length of the exported string
RemoveEmptyJson(jsonObj);
var jsonStr = JsonUtils.Serialize(jsonObj, false);
var encodedStr = Utils.Base64Encode(jsonStr).Replace('+', '-').Replace('/', '_').Replace("=", "");
return $"{Global.InnerUriProtocol}{item.ConfigType.ToString().ToLower()}/{encodedStr}";
}
private static string GetReproducibleExportId(string originalIndexId)
{
if (originalIndexId.IsNullOrEmpty())
{
return originalIndexId;
}
var hash = HashCode.Combine(SessionSalt.Value, originalIndexId) & 0x7FFFFFFF;
var bytes = BitConverter.GetBytes(hash);
return Convert.ToBase64String(bytes).Replace("=", "");
}
private static void RemoveEmptyJson(JsonNode? node)
{
// ReSharper disable once ConvertIfStatementToSwitchStatement
if (node is JsonObject jsonObject)
{
var propertiesToRemove = new List<string>();
foreach (var property in jsonObject)
{
RemoveEmptyJson(property.Value);
if (IsEmpty(property.Value))
{
propertiesToRemove.Add(property.Key);
}
}
foreach (var key in propertiesToRemove)
{
jsonObject.Remove(key);
}
}
else if (node is JsonArray jsonArray)
{
for (var i = jsonArray.Count - 1; i >= 0; i--)
{
RemoveEmptyJson(jsonArray[i]);
if (IsEmpty(jsonArray[i]))
{
jsonArray.RemoveAt(i);
}
}
}
}
private static bool IsEmpty(JsonNode? node)
{
return node switch
{
null => true,
JsonValue value when value.TryGetValue<string>(out var str) => string.IsNullOrEmpty(str),
JsonObject obj => obj.Count == 0,
JsonArray arr => arr.Count == 0,
_ => false
};
}
}
+172 -8
View File
@@ -27,9 +27,10 @@ public class WireguardFmt : BaseFmt
item.SetProtocolExtra(item.GetProtocolExtra() with
{
WgPublicKey = GetQueryDecoded(query, "publickey"),
WgPresharedKey = GetQueryDecoded(query, "presharedkey"),
WgReserved = GetQueryDecoded(query, "reserved"),
WgInterfaceAddress = GetQueryDecoded(query, "address"),
WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280,
WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : null,
});
return item;
@@ -48,20 +49,183 @@ public class WireguardFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var protoExtra = item.GetProtocolExtra();
var dicQuery = new Dictionary<string, string>();
if (!item.GetProtocolExtra().WgPublicKey.IsNullOrEmpty())
if (!protoExtra.WgPublicKey.IsNullOrEmpty())
{
dicQuery.Add("publickey", Utils.UrlEncode(item.GetProtocolExtra().WgPublicKey));
dicQuery.Add("publickey", Utils.UrlEncode(protoExtra.WgPublicKey));
}
if (!item.GetProtocolExtra().WgReserved.IsNullOrEmpty())
if (!protoExtra.WgPresharedKey.IsNullOrEmpty())
{
dicQuery.Add("reserved", Utils.UrlEncode(item.GetProtocolExtra().WgReserved));
dicQuery.Add("presharedkey", Utils.UrlEncode(protoExtra.WgPresharedKey));
}
if (!item.GetProtocolExtra().WgInterfaceAddress.IsNullOrEmpty())
if (!protoExtra.WgReserved.IsNullOrEmpty())
{
dicQuery.Add("address", Utils.UrlEncode(item.GetProtocolExtra().WgInterfaceAddress));
dicQuery.Add("reserved", Utils.UrlEncode(protoExtra.WgReserved));
}
if (!protoExtra.WgInterfaceAddress.IsNullOrEmpty())
{
dicQuery.Add("address", Utils.UrlEncode(protoExtra.WgInterfaceAddress));
}
if (protoExtra.WgMtu > 0)
{
dicQuery.Add("mtu", protoExtra.WgMtu.ToString());
}
dicQuery.Add("mtu", Utils.UrlEncode(item.GetProtocolExtra().WgMtu > 0 ? item.GetProtocolExtra().WgMtu.ToString() : "1280"));
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Password, dicQuery, remark);
}
public static List<ProfileItem>? ResolveConfig(string strData)
{
var interfaceDic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var peerDicList = new List<Dictionary<string, string>>();
var currentDicRef = interfaceDic;
using (var reader = new StringReader(strData))
{
while (reader.ReadLine() is { } line)
{
if (line.IsNullOrEmpty())
{
continue;
}
var trimmedLine = line.Trim();
if (trimmedLine.Equals("[Interface]", StringComparison.OrdinalIgnoreCase))
{
currentDicRef = interfaceDic;
continue;
}
if (trimmedLine.Equals("[Peer]", StringComparison.OrdinalIgnoreCase))
{
var peerDic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
peerDicList.Add(peerDic);
currentDicRef = peerDic;
continue;
}
if (trimmedLine.StartsWith('[') || trimmedLine.StartsWith('#') || trimmedLine.StartsWith(';'))
{
continue;
}
var idx = line.IndexOf('=');
if (idx <= 0)
{
continue;
}
var key = line[..idx].Trim();
var value = line[(idx + 1)..].Trim();
var commentPos = value.IndexOfAny(['#', ';']);
if (commentPos >= 0)
{
value = value[..commentPos].TrimEnd();
}
currentDicRef[key] = value;
}
}
if (!interfaceDic.TryGetValue("PrivateKey", out var privateKey) || privateKey.IsNullOrEmpty())
{
return null;
}
var wgMtu = interfaceDic.TryGetValue("MTU", out var mtuStr) && int.TryParse(mtuStr, out var mtuVal) ? mtuVal : 0;
var wgInterfaceAddress = interfaceDic.TryGetValue("Address", out var interfaceAddress) ? interfaceAddress : string.Empty;
var index = 0;
var resultList = new List<ProfileItem>();
foreach (var peerDic in peerDicList)
{
if (!peerDic.TryGetValue("Endpoint", out var endpoint) || endpoint.IsNullOrEmpty())
{
continue;
}
if (!TryParseEndpoint(endpoint, out var peerAddress, out var peerPort))
{
continue;
}
var protoExtra = new ProtocolExtraItem
{
WgPublicKey = (peerDic.TryGetValue("PublicKey", out var publicKey) ? publicKey : string.Empty).NullIfEmpty(),
WgPresharedKey = (peerDic.TryGetValue("PresharedKey", out var presharedKey) ? presharedKey : string.Empty).NullIfEmpty(),
WgInterfaceAddress = wgInterfaceAddress,
WgReserved = (peerDic.TryGetValue("Reserved", out var reserved) ? reserved : string.Empty).NullIfEmpty(),
WgMtu = wgMtu > 0 ? wgMtu : null,
};
var item = new ProfileItem
{
Remarks = $"{nameof(EConfigType.WireGuard)} Peer {index + 1}",
ConfigType = EConfigType.WireGuard,
Address = peerAddress,
Port = peerPort,
Password = privateKey,
};
item.SetProtocolExtra(protoExtra);
resultList.Add(item);
index += 1;
}
return resultList;
}
private static bool TryParseEndpoint(string endpoint, out string address, out int port)
{
address = string.Empty;
port = 2408;
var trimmedEndpoint = endpoint.Trim();
if (trimmedEndpoint.IsNullOrEmpty())
{
return false;
}
if (trimmedEndpoint[0] == '[')
{
var closeIndex = trimmedEndpoint.IndexOf(']');
if (closeIndex <= 1)
{
return false;
}
address = trimmedEndpoint[1..closeIndex].Trim();
var portIndex = closeIndex + 1;
if (portIndex < trimmedEndpoint.Length && trimmedEndpoint[portIndex] == ':' &&
int.TryParse(trimmedEndpoint[(portIndex + 1)..].Trim(), out var bracketedPort) && bracketedPort is > 0 and <= 65535)
{
port = bracketedPort;
}
return address.IsNotEmpty();
}
var lastColonIndex = trimmedEndpoint.LastIndexOf(':');
if (lastColonIndex <= 0)
{
address = trimmedEndpoint;
return true;
}
address = trimmedEndpoint[..lastColonIndex].Trim();
var portText = trimmedEndpoint[(lastColonIndex + 1)..].Trim();
if (address.IsNullOrEmpty())
{
return false;
}
if (int.TryParse(portText, out var parsedPortValue) && parsedPortValue is > 0 and <= 65535)
{
port = parsedPortValue;
return true;
}
address = trimmedEndpoint;
return true;
}
}
+2
View File
@@ -17,6 +17,8 @@ public class CoreBasicItem
public string? SendThrough { get; set; }
public string? BindInterface { get; set; }
public bool EnableFragment { get; set; }
public bool EnableCacheFile4Sbox { get; set; } = true;
@@ -17,4 +17,7 @@ public record CoreConfigContext
// TUN Compatibility
public bool IsTunEnabled { get; init; } = false;
public HashSet<string> ProtectDomainList { get; init; } = [];
public bool IsWindows { get; init; }
public bool IsMacOS { get; init; }
}
+1 -1
View File
@@ -173,7 +173,7 @@ public class Peer4Sbox
public string? pre_shared_key { get; set; }
public List<string> allowed_ips { get; set; }
public int? persistent_keepalive_interval { get; set; }
public List<int> reserved { get; set; }
public List<int>? reserved { get; set; }
}
public class Tls4Sbox
+1
View File
@@ -163,6 +163,7 @@ public class WireguardPeer4Ray
{
public string endpoint { get; set; }
public string publicKey { get; set; }
public string? preSharedKey { get; set; }
}
public class VnextItem4Ray
+46 -10
View File
@@ -1023,6 +1023,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Export v2rayN Internal Share Link to Clipboard 的本地化字符串。
/// </summary>
public static string menuExport2InnerUri {
get {
return ResourceManager.GetString("menuExport2InnerUri", resourceCulture);
}
}
/// <summary>
/// 查找类似 Export Share Link to Clipboard 的本地化字符串。
/// </summary>
@@ -3222,6 +3231,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 MTU 的本地化字符串。
/// </summary>
public static string TbMtu {
get {
return ResourceManager.GetString("TbMtu", resourceCulture);
}
}
/// <summary>
/// 查找类似 Transport protocol(network) 的本地化字符串。
/// </summary>
@@ -3312,6 +3330,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 PreSharedKey 的本地化字符串。
/// </summary>
public static string TbPreSharedKey {
get {
return ResourceManager.GetString("TbPreSharedKey", resourceCulture);
}
}
/// <summary>
/// 查找类似 Socks port 的本地化字符串。
/// </summary>
@@ -3430,7 +3457,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Reserved (2,3,4) 的本地化字符串。
/// 查找类似 Reserved 的本地化字符串。
/// </summary>
public static string TbReserved {
get {
@@ -3681,6 +3708,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Bind Interface 的本地化字符串。
/// </summary>
public static string TbSettingsBindInterface {
get {
return ResourceManager.GetString("TbSettingsBindInterface", resourceCulture);
}
}
/// <summary>
/// 查找类似 For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode 的本地化字符串。
/// </summary>
public static string TbSettingsBindInterfaceTip {
get {
return ResourceManager.GetString("TbSettingsBindInterfaceTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Users in China region can ignore this item 的本地化字符串。
/// </summary>
@@ -4347,15 +4392,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 MTU 的本地化字符串。
/// </summary>
public static string TbSettingsTunMtu {
get {
return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture);
}
}
/// <summary>
/// 查找类似 Stack 的本地化字符串。
/// </summary>
+23 -5
View File
@@ -1062,7 +1062,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>پشته شبکه</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1075,7 +1075,7 @@
<value>کلید خصوصی</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Reserved (2,3,4)</value>
<value>Reserved</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>آدرس (IPv4, IPv6)</value>
@@ -1689,9 +1689,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbLegacyProtect" xml:space="preserve">
<value>Legacy TUN Protect</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
</data>
<data name="TbCamouflageDomain" xml:space="preserve">
<value>Camouflage domain</value>
</data>
@@ -1713,4 +1710,25 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP Test Url</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Local outbound address (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Please fill in the correct IPv4 address for SendThrough.</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>Bind Interface</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
</root>
+29 -11
View File
@@ -1059,7 +1059,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Pile de protocoles</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1072,7 +1072,7 @@
<value>PrivateKey</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Reserved (2,3,4)</value>
<value>Reserved</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address (IPv4,IPv6)</value>
@@ -1311,15 +1311,6 @@
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>Le mot de passe sera vérifié en ligne de commande. En cas d’échec ou de dysfonctionnement, redémarrez lapplication. Il nest pas stocké et doit être saisi à chaque redémarrage.</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Adresse sortante locale (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale.</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Veuillez saisir ladresse IPv4 correcte de SendThrough.</value>
</data>
<data name="TransportHeaderType5" xml:space="preserve">
<value>Mode XHTTP</value>
</data>
@@ -1710,4 +1701,31 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbAllowInsecureCertFetchTips" xml:space="preserve">
<value>Only for fetching self-signed certificates. This may expose you to MITM risks.</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Adresse sortante locale (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Veuillez saisir ladresse IPv4 correcte de SendThrough.</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>Lier l'interface</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>Pour les environnements multi-interfaces, entrez le nom de l'interface à lier. Ne fonctionne que sur les systèmes Windows et en mode TUN</value>
</data>
<data name="menuUdpTestServer" xml:space="preserve">
<value>Test Configurations UDP Delay</value>
</data>
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP Test Url</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
</root>
+23 -5
View File
@@ -1062,7 +1062,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Hálózati verem</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1075,7 +1075,7 @@
<value>Privát kulcs</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Fenntartott (2,3,4)</value>
<value>Fenntartott</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Cím (IPv4, IPv6)</value>
@@ -1689,9 +1689,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbLegacyProtect" xml:space="preserve">
<value>Legacy TUN Protect</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
</data>
<data name="TbCamouflageDomain" xml:space="preserve">
<value>Álcázási tartomány</value>
</data>
@@ -1713,4 +1710,25 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP Test Url</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Local outbound address (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Please fill in the correct IPv4 address for SendThrough.</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>Bind Interface</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
</root>
+24 -12
View File
@@ -1062,7 +1062,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Stack</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1075,7 +1075,7 @@
<value>Private Key</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Reserved (2,3,4)</value>
<value>Reserved</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address (IPv4, IPv6)</value>
@@ -1314,15 +1314,6 @@
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart.</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Local outbound address (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Please fill in the correct IPv4 address for SendThrough.</value>
</data>
<data name="TransportHeaderType5" xml:space="preserve">
<value>xhttp mode</value>
</data>
@@ -1719,4 +1710,25 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP Test Url</value>
</data>
</root>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Local outbound address (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Please fill in the correct IPv4 address for SendThrough.</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>Bind Interface</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
</root>
+21 -9
View File
@@ -1062,7 +1062,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Сетевой стек</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1075,7 +1075,7 @@
<value>Приватный ключ</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Зарезервировано (2, 3, 4)</value>
<value>Зарезервировано</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Адрес (IPv4, IPv6)</value>
@@ -1689,9 +1689,6 @@
<data name="TbLegacyProtect" xml:space="preserve">
<value>Устаревшая защита TUN (Legacy Protect)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>Для среды с несколькими сетевыми интерфейсами укажите IPv4-адрес локального компьютера</value>
</data>
<data name="TbCamouflageDomain" xml:space="preserve">
<value>Камуфляжный домен</value>
</data>
@@ -1713,10 +1710,25 @@
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>URL для UDP-теста</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Укажите корректный IPv4-адрес для SendThrough.</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>Локальный исходящий адрес (SendThrough)</value>
</data>
</root>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>Для среды с несколькими сетевыми интерфейсами укажите IPv4-адрес локального компьютера</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>Укажите корректный IPv4-адрес для SendThrough.</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>Привязать интерфейс</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для привязки. Работает только в Windows и режиме TUN</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
</root>
+24 -12
View File
@@ -1059,7 +1059,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>协议栈</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1072,7 +1072,7 @@
<value>PrivateKey</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Reserved (2,3,4)</value>
<value>Reserved</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address (IPv4,IPv6)</value>
@@ -1311,15 +1311,6 @@
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>本地出站地址 (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>用于多网口环境,请填写本机 IPv4 地址</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>请填写正确的 SendThrough IPv4 地址。</value>
</data>
<data name="TransportHeaderType5" xml:space="preserve">
<value>XHTTP 模式</value>
</data>
@@ -1716,4 +1707,25 @@
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP 测试地址</value>
</data>
</root>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>本地出站地址 (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>用于多网口环境,请填写本机 IPv4 地址</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>请填写正确的 SendThrough IPv4 地址。</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>绑定网口</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>用于多网口环境,填写要绑定的网口名称,仅生效于 Windows 系统或 TUN 模式</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>导出 v2rayN 内部分享链接至剪贴板 (多选)</value>
</data>
</root>
+26 -8
View File
@@ -1059,7 +1059,7 @@
<data name="TbSettingsTunStack" xml:space="preserve">
<value>協定堆疊</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<data name="TbMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
@@ -1072,7 +1072,7 @@
<value>PrivateKey</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Reserved (2,3,4)</value>
<value>Reserved</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address (Ipv4,Ipv6)</value>
@@ -1684,10 +1684,7 @@
<value>ICMP 路由策略</value>
</data>
<data name="TbLegacyProtect" xml:space="preserve">
<value>Legacy TUN Protect</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
<value>舊版 TUN 保護</value>
</data>
<data name="TbCamouflageDomain" xml:space="preserve">
<value>偽裝域名</value>
@@ -1705,9 +1702,30 @@
<value>僅用於抓取自簽證書,存在中間人風險。</value>
</data>
<data name="menuUdpTestServer" xml:space="preserve">
<value>Test Configurations UDP Delay</value>
<value>測試 UDP 延遲(多選)</value>
</data>
<data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP Test Url</value>
<value>UDP 測試網址</value>
</data>
<data name="TbSettingsSendThrough" xml:space="preserve">
<value>本機出站位址 (SendThrough)</value>
</data>
<data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>適用於多網路介面環境,請填寫本機 IPv4 位址</value>
</data>
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
<value>請填寫正確的 SendThrough IPv4 位址。</value>
</data>
<data name="TbSettingsBindInterface" xml:space="preserve">
<value>綁定網路介面</value>
</data>
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
<value>適用於多網路介面環境,請填寫要綁定的介面名稱;Windows 系統有效,其他系統僅在 TUN 模式下生效</value>
</data>
<data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value>
</data>
<data name="menuExport2InnerUri" xml:space="preserve">
<value>匯出 v2rayN 內部分享連結至剪貼簿(多選)</value>
</data>
</root>
@@ -57,6 +57,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
ConvertGeo2Ruleset();
ApplyOutboundBindInterface();
ApplyOutboundSendThrough();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
@@ -170,6 +171,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
_coreConfig.route.rules.Add(rule);
}
ApplyOutboundBindInterface();
ApplyOutboundSendThrough();
ret.Success = true;
ret.Data = JsonUtils.Serialize(_coreConfig);
@@ -229,6 +231,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
listen_port = port,
type = EInboundProtocol.mixed.ToString(),
});
ApplyOutboundBindInterface();
ApplyOutboundSendThrough();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
@@ -59,22 +59,38 @@ public partial class CoreConfigSingboxService
return JsonUtils.Serialize(fullConfigTemplateNode);
}
private void ApplyOutboundSendThrough()
private void ApplyOutboundBindInterface()
{
var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx();
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
if (bindInterface.IsNullOrEmpty())
{
return;
}
if (!(context.IsTunEnabled || context.IsWindows))
{
return;
}
foreach (var outbound in _coreConfig.outbounds ?? [])
{
outbound.inet4_bind_address = ShouldApplySendThrough(outbound, sendThrough) ? sendThrough : null;
outbound.bind_interface = ShouldBindNet(outbound) ? bindInterface : null;
}
}
private static bool ShouldApplySendThrough(Outbound4Sbox outbound, string? sendThrough)
private void ApplyOutboundSendThrough()
{
var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx();
if (sendThrough.IsNullOrEmpty())
{
return false;
return;
}
foreach (var outbound in _coreConfig.outbounds ?? [])
{
outbound.inet4_bind_address = ShouldBindNet(outbound) ? sendThrough : null;
}
}
private static bool ShouldBindNet(Outbound4Sbox outbound)
{
if (outbound.type is "direct" or "block" or "dns" or "selector" or "urltest")
{
return false;
@@ -298,7 +298,7 @@ public partial class CoreConfigSingboxService
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>();
var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>();
var regionName = string.Empty;
if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
{
@@ -310,16 +310,16 @@ public partial class CoreConfigSingboxService
foreach (var ip in ipItems)
{
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
if (ip.StartsWith(Global.GeoIPPrefix, StringComparison.OrdinalIgnoreCase))
{
var region = ip["geoip:".Length..];
if (!string.IsNullOrEmpty(region))
var region = ip[Global.GeoIPPrefix.Length..];
if (string.IsNullOrEmpty(region))
{
expectedIPsRegions.Add(region);
regionNames.Add(region);
regionNames.Add($"geolocation-{region}");
regionNames.Add($"tld-{region}");
continue;
}
expectedIPsRegions.Add(region);
regionName = region;
}
else
{
@@ -352,19 +352,25 @@ public partial class CoreConfigSingboxService
rule.server = Global.SingboxDirectDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0 && !regionName.IsNullOrEmpty())
{
var geositeSet = new HashSet<string>(rule.geosite);
if (regionNames.Intersect(geositeSet).Any())
var regionGeosite = rule.geosite.Where(g => g.EndsWith($"-{regionName}", StringComparison.OrdinalIgnoreCase)
|| g.EndsWith($"@{regionName}", StringComparison.OrdinalIgnoreCase)
|| g == regionName).ToList();
if (regionGeosite.Count > 0)
{
rule.geosite.RemoveAll(regionGeosite.Contains);
var rule4ExpectedIPs = JsonUtils.DeepCopy(rule);
rule4ExpectedIPs.geosite = regionGeosite;
if (expectedIPsRegions.Count > 0)
{
rule.geoip = expectedIPsRegions;
rule4ExpectedIPs.geoip = expectedIPsRegions;
}
if (expectedIPCidr.Count > 0)
{
rule.ip_cidr = expectedIPCidr;
rule4ExpectedIPs.ip_cidr = expectedIPCidr;
}
_coreConfig.dns.rules.Add(rule4ExpectedIPs);
}
}
}
@@ -8,10 +8,10 @@ public partial class CoreConfigSingboxService
{
var listen = "0.0.0.0";
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var isUsingLocalMixedPort = _node.Address == Global.Loopback && _node.Port == listenPort;
_coreConfig.inbounds = [];
if (!context.IsTunEnabled
|| (context.IsTunEnabled && _node.Address != Global.Loopback && _node.Port != listenPort))
if (!context.IsTunEnabled || !isUsingLocalMixedPort)
{
var inbound = new Inbound4Sbox()
{
@@ -62,7 +62,7 @@ public partial class CoreConfigSingboxService
}
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
tunInbound.interface_name = Utils.IsMacOS() ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.interface_name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
@@ -227,7 +227,7 @@ public partial class CoreConfigSingboxService
: _config.HysteriaItem.UpMbps;
int? downMbps = protocolExtra?.DownMbps is { } sd and >= 0
? sd
: _config.HysteriaItem.UpMbps;
: _config.HysteriaItem.DownMbps;
outbound.up_mbps = upMbps > 0 ? upMbps : null;
outbound.down_mbps = downMbps > 0 ? downMbps : null;
var ports = protocolExtra?.Ports?.IsNullOrEmpty() == false ? protocolExtra.Ports : null;
@@ -309,7 +309,7 @@ public partial class CoreConfigSingboxService
{
var protocolExtra = _node.GetProtocolExtra();
endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress);
endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"];
endpoint.type = Global.ProtocolTypes[_node.ConfigType];
switch (_node.ConfigType)
@@ -318,13 +318,12 @@ public partial class CoreConfigSingboxService
{
var peer = new Peer4Sbox
{
public_key = protocolExtra.WgPublicKey,
public_key = protocolExtra.WgPublicKey ?? string.Empty,
pre_shared_key = protocolExtra.WgPresharedKey,
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(),
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(s => s.Trim()).Select(int.Parse).ToList(),
address = _node.Address,
port = _node.Port,
// TODO default ["0.0.0.0/0", "::/0"]
allowed_ips = new() { "0.0.0.0/0", "::/0" },
allowed_ips = ["0.0.0.0/0", "::/0"],
};
endpoint.private_key = _node.Password;
endpoint.mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First();
@@ -329,11 +329,52 @@ public partial class CoreConfigSingboxService
if (item.Ip?.Count > 0)
{
var countIp = 0;
foreach (var it in item.Ip)
var negativeIpList = item.Ip.Where(it => it.StartsWith('!')).ToList();
if (negativeIpList.Count > 0)
{
if (ParseV2Address(it, rule2))
var positiveIpList = item.Ip.Except(negativeIpList).ToList();
var positiveRule = rule2;
positiveRule = JsonUtils.DeepCopy(rule2);
positiveRule.outbound = null;
positiveRule.action = null;
foreach (var it in positiveIpList)
{
countIp++;
if (ParseV2Address(it, positiveRule))
{
countIp++;
}
}
var negativeRule = new Rule4Sbox();
foreach (var it in negativeIpList)
{
// Remove first '!' and trim spaces
var ip = it[1..].Trim();
if (ParseV2Address(ip, negativeRule))
{
countIp++;
}
}
negativeRule.invert = true;
rule2 = new Rule4Sbox()
{
outbound = rule2.outbound,
action = rule2.action,
type = "logical",
mode = "or",
rules = [
positiveRule,
negativeRule
]
};
}
else
{
foreach (var it in item.Ip)
{
if (ParseV2Address(it, rule2))
{
countIp++;
}
}
}
if (countIp > 0)
@@ -406,10 +447,10 @@ public partial class CoreConfigSingboxService
{
return false;
}
else if (domain.StartsWith("geosite:"))
else if (domain.StartsWith(Global.GeoSitePrefix))
{
rule.geosite ??= [];
rule.geosite?.Add(domain.Substring(8));
rule.geosite?.Add(domain[Global.GeoSitePrefix.Length..]);
}
else if (domain.StartsWith("regexp:"))
{
@@ -450,28 +491,18 @@ public partial class CoreConfigSingboxService
{
return false;
}
else if (address.Equals("geoip:private"))
else if (address.Equals($"{Global.GeoIPPrefix}private"))
{
rule.ip_is_private = true;
}
else if (address.StartsWith("geoip:"))
else if (address.StartsWith(Global.GeoIPPrefix))
{
rule.geoip ??= new();
rule.geoip?.Add(address.Substring(6));
}
else if (address.Equals("geoip:!private"))
{
rule.ip_is_private = false;
}
else if (address.StartsWith("geoip:!"))
{
rule.geoip ??= new();
rule.geoip?.Add(address.Substring(6));
rule.invert = true;
rule.geoip ??= [];
rule.geoip?.Add(address[Global.GeoIPPrefix.Length..]);
}
else
{
rule.ip_cidr ??= new();
rule.ip_cidr ??= [];
rule.ip_cidr?.Add(address);
}
return true;
@@ -60,6 +60,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{
ApplyOutboundFragment();
}
ApplyOutboundBindInterface();
ApplyOutboundSendThrough();
var finalRule = BuildFinalRule();
@@ -203,6 +204,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{
ApplyOutboundFragment();
}
ApplyOutboundBindInterface();
ApplyOutboundSendThrough();
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
ret.Success = true;
@@ -274,6 +276,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{
ApplyOutboundFragment();
}
ApplyOutboundBindInterface();
ApplyOutboundSendThrough();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
@@ -134,22 +134,59 @@ public partial class CoreConfigV2rayService
return JsonUtils.Serialize(fullConfigTemplateNode);
}
private void ApplyOutboundSendThrough()
private void ApplyOutboundBindInterface()
{
var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx();
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
if (bindInterface.IsNullOrEmpty())
{
return;
}
if (!(context.IsTunEnabled || context.IsWindows))
{
return;
}
foreach (var outbound in _coreConfig.outbounds ?? [])
{
outbound.sendThrough = ShouldApplySendThrough(outbound, sendThrough) ? sendThrough : null;
if (!ShouldBindNet(outbound))
{
continue;
}
outbound.streamSettings ??= new();
outbound.streamSettings.sockopt ??= new();
outbound.streamSettings.sockopt.Interface = bindInterface;
// xhttp download bind interface
if (outbound?.streamSettings?.xhttpSettings?.extra is null)
{
continue;
}
var xhttpExtra = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings.xhttpSettings!.extra));
if (xhttpExtra is not JsonObject xhttpExtraObject
|| xhttpExtraObject["downloadSettings"] is not JsonObject downloadSettings)
{
continue;
}
var sockopt = downloadSettings["sockopt"] as JsonObject ?? new JsonObject();
sockopt["interface"] = bindInterface;
downloadSettings["sockopt"] = sockopt;
outbound.streamSettings.xhttpSettings.extra = xhttpExtraObject;
}
}
private static bool ShouldApplySendThrough(Outbounds4Ray outbound, string? sendThrough)
private void ApplyOutboundSendThrough()
{
var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx();
if (sendThrough.IsNullOrEmpty())
{
return false;
return;
}
foreach (var outbound in _coreConfig.outbounds ?? [])
{
outbound.sendThrough = ShouldBindNet(outbound) ? sendThrough : null;
}
}
private static bool ShouldBindNet(Outbounds4Ray outbound)
{
if (outbound.protocol is "freedom" or "blackhole" or "dns" or "loopback")
{
return false;
@@ -121,7 +121,7 @@ public partial class CoreConfigV2rayService
var proxyGeositeList = new List<string>();
var expectedDomainList = new List<string>();
var expectedIPs = new List<string>();
var regionNames = new HashSet<string>();
var regionName = string.Empty;
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.First());
var dnsServerDomains = new List<string>();
@@ -160,18 +160,14 @@ public partial class CoreConfigV2rayService
.Where(s => !string.IsNullOrEmpty(s))
.ToList();
foreach (var ip in expectedIPs)
foreach (var region in from ip in expectedIPs
where ip.StartsWith(Global.GeoIPPrefix, StringComparison.OrdinalIgnoreCase)
select ip[Global.GeoIPPrefix.Length..]
into region
where !string.IsNullOrEmpty(region)
select region)
{
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
{
var region = ip["geoip:".Length..];
if (!string.IsNullOrEmpty(region))
{
regionNames.Add($"geosite:{region}");
regionNames.Add($"geosite:geolocation-{region}");
regionNames.Add($"geosite:tld-{region}");
}
}
regionName = region;
}
}
@@ -201,9 +197,14 @@ public partial class CoreConfigV2rayService
if (item.OutboundTag == Global.DirectTag)
{
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
if (normalizedDomain.StartsWith(Global.GeoSitePrefix) || normalizedDomain.StartsWith("ext:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
var isExpectedDomain = !regionName.IsNullOrEmpty()
&& (normalizedDomain.EndsWith($"-{regionName}")
|| normalizedDomain.EndsWith($"@{regionName}")
|| normalizedDomain == Global.GeoSitePrefix + regionName);
var targetList = isExpectedDomain ? expectedDomainList : directGeositeList;
targetList.Add(normalizedDomain);
}
else
{
@@ -212,7 +213,7 @@ public partial class CoreConfigV2rayService
}
else if (item.OutboundTag != Global.BlockTag)
{
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
if (normalizedDomain.StartsWith(Global.GeoSitePrefix) || normalizedDomain.StartsWith("ext:"))
{
proxyGeositeList.Add(normalizedDomain);
}
@@ -10,9 +10,9 @@ public partial class CoreConfigV2rayService
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
_coreConfig.inbounds = [];
var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
var isUsingLocalMixedPort = _node.Address == Global.Loopback && _node.Port == listenPort;
if (!context.IsTunEnabled
|| (context.IsTunEnabled && _node.Address != Global.Loopback && _node.Port != listenPort))
if (!context.IsTunEnabled || !isUsingLocalMixedPort)
{
_coreConfig.inbounds.Add(inbound);
@@ -54,12 +54,17 @@ public partial class CoreConfigV2rayService
_config.TunModeItem.Mtu = Global.TunMtus.First();
}
var tunInbound = JsonUtils.Deserialize<Inbounds4Ray>(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? new Inbounds4Ray { };
tunInbound.settings.name = Utils.IsMacOS() ? $"utun{new Random().Next(99)}" : "xray_tun";
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
if (_config.TunModeItem.EnableIPv6Address == false)
{
tunInbound.settings.gateway = ["172.18.0.1/30"];
}
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
if (!bindInterface.IsNullOrEmpty())
{
tunInbound.settings.autoOutboundsInterface = bindInterface;
}
tunInbound.sniffing = inbound.sniffing;
_coreConfig.inbounds.Add(tunInbound);
}
@@ -258,13 +258,14 @@ public partial class CoreConfigV2rayService
var peer = new WireguardPeer4Ray
{
publicKey = protocolExtra.WgPublicKey ?? "",
endpoint = address + ":" + _node.Port.ToString()
endpoint = address + ":" + _node.Port.ToString(),
preSharedKey = protocolExtra.WgPresharedKey,
};
var setting = new Outboundsettings4Ray
{
address = Utils.String2List(protocolExtra.WgInterfaceAddress),
address = Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"],
secretKey = _node.Password,
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(),
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(s => s.Trim()).Select(int.Parse).ToList(),
mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(),
peers = [peer]
};
@@ -576,7 +577,7 @@ public partial class CoreConfigV2rayService
: _config.HysteriaItem.UpMbps;
int? downMbps = protocolExtra?.DownMbps is { } sd and >= 0
? sd
: _config.HysteriaItem.UpMbps;
: _config.HysteriaItem.DownMbps;
var hopInterval = !protocolExtra.HopInterval.IsNullOrEmpty()
? protocolExtra.HopInterval
: (_config.HysteriaItem.HopInterval >= 5
+2 -2
View File
@@ -375,8 +375,8 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules ?? [])
{
AddPrefixedItems(item.Ip, "geoip:", geoipFiles);
AddPrefixedItems(item.Domain, "geosite:", geoSiteFiles);
AddPrefixedItems(item.Ip, Global.GeoIPPrefix, geoipFiles);
AddPrefixedItems(item.Domain, Global.GeoSitePrefix, geoSiteFiles);
}
}
@@ -53,8 +53,9 @@ public class AddServerViewModel : MyReactiveObject
[Reactive]
public string WgPublicKey { get; set; }
//[Reactive]
//public string WgPresharedKey { get; set; }
[Reactive]
public string WgPresharedKey { get; set; }
[Reactive]
public string WgInterfaceAddress { get; set; }
@@ -159,17 +160,8 @@ public class AddServerViewModel : MyReactiveObject
switch (SelectedSource.GetNetwork())
{
case nameof(ETransport.raw):
Host = value;
break;
case nameof(ETransport.ws):
Host = value;
break;
case nameof(ETransport.httpupgrade):
Host = value;
break;
case nameof(ETransport.xhttp):
Host = value;
break;
@@ -202,13 +194,7 @@ public class AddServerViewModel : MyReactiveObject
break;
case nameof(ETransport.ws):
Path = value;
break;
case nameof(ETransport.httpupgrade):
Path = value;
break;
case nameof(ETransport.xhttp):
Path = value;
break;
@@ -303,6 +289,7 @@ public class AddServerViewModel : MyReactiveObject
VlessEncryption = protocolExtra.VlessEncryption?.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None;
SsMethod = protocolExtra.SsMethod ?? string.Empty;
WgPublicKey = protocolExtra.WgPublicKey ?? string.Empty;
WgPresharedKey = protocolExtra.WgPresharedKey ?? string.Empty;
WgInterfaceAddress = protocolExtra.WgInterfaceAddress ?? string.Empty;
WgReserved = protocolExtra.WgReserved ?? string.Empty;
WgMtu = protocolExtra.WgMtu ?? 1280;
@@ -401,6 +388,7 @@ public class AddServerViewModel : MyReactiveObject
VlessEncryption = VlessEncryption.NullIfEmpty(),
SsMethod = SsMethod.NullIfEmpty(),
WgPublicKey = WgPublicKey.NullIfEmpty(),
WgPresharedKey = WgPresharedKey.NullIfEmpty(),
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
WgReserved = WgReserved.NullIfEmpty(),
WgMtu = WgMtu >= 576 ? WgMtu : null,
@@ -21,6 +21,7 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public string defFingerprint { get; set; }
[Reactive] public string defUserAgent { get; set; }
[Reactive] public string sendThrough { get; set; }
[Reactive] public string bindInterface { get; set; }
[Reactive] public string mux4SboxProtocol { get; set; }
[Reactive] public bool enableCacheFile4Sbox { get; set; }
[Reactive] public int? hyUpMbps { get; set; }
@@ -156,7 +157,8 @@ public class OptionSettingViewModel : MyReactiveObject
defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure;
defFingerprint = _config.CoreBasicItem.DefFingerprint;
defUserAgent = _config.CoreBasicItem.DefUserAgent;
sendThrough = _config.CoreBasicItem.SendThrough;
sendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty;
bindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty;
mux4SboxProtocol = _config.Mux4SboxItem.Protocol;
enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox;
hyUpMbps = _config.HysteriaItem.UpMbps;
@@ -301,7 +303,7 @@ public class OptionSettingViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
return;
}
var sendThroughValue = sendThrough?.TrimEx();
var sendThroughValue = sendThrough.TrimEx();
if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue))
{
NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4);
@@ -346,7 +348,8 @@ public class OptionSettingViewModel : MyReactiveObject
_config.CoreBasicItem.DefAllowInsecure = defAllowInsecure;
_config.CoreBasicItem.DefFingerprint = defFingerprint;
_config.CoreBasicItem.DefUserAgent = defUserAgent;
_config.CoreBasicItem.SendThrough = sendThrough?.TrimEx();
_config.CoreBasicItem.SendThrough = sendThrough.TrimEx();
_config.CoreBasicItem.BindInterface = bindInterface.TrimEx();
_config.Mux4SboxItem.Protocol = mux4SboxProtocol;
_config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox;
_config.HysteriaItem.UpMbps = hyUpMbps ?? 0;
@@ -72,6 +72,7 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> Export2ClientConfigClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> Export2ShareUrlCmd { get; }
public ReactiveCommand<Unit, Unit> Export2ShareUrlBase64Cmd { get; }
public ReactiveCommand<Unit, Unit> Export2InnerUriCmd { get; }
public ReactiveCommand<Unit, Unit> AddSubCmd { get; }
public ReactiveCommand<Unit, Unit> EditSubCmd { get; }
@@ -212,6 +213,10 @@ public class ProfilesViewModel : MyReactiveObject
{
await Export2ShareUrlAsync(true);
}, canEditRemove);
Export2InnerUriCmd = ReactiveCommand.CreateFromTask(async () =>
{
await Export2InnerUrlAsync();
}, canEditRemove);
//Subscription
AddSubCmd = ReactiveCommand.CreateFromTask(async () =>
@@ -840,6 +845,32 @@ public class ProfilesViewModel : MyReactiveObject
}
}
public async Task Export2InnerUrlAsync()
{
var lstSelected = await GetProfileItems(true);
if (lstSelected == null)
{
return;
}
var result = string.Empty;
await Task.Run(() =>
{
result = InnerFmt.ToUri(lstSelected);
});
if (!result.IsNullOrEmpty())
{
await _updateView?.Invoke(EViewAction.SetClipboardData, result);
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
}
else
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
}
}
#endregion Add Servers
#region Subscription
@@ -508,7 +508,7 @@
Grid.Row="2"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
@@ -542,43 +542,57 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbReserved}" />
Text="{x:Static resx:ResUI.TbPreSharedKey}" />
<TextBox
x:Name="txtPath9"
x:Name="txtPreSharedKey9"
Grid.Row="3"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Watermark="2,3,4" />
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbReserved}" />
<TextBox
x:Name="txtPath9"
Grid.Row="4"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Watermark="0, 0, 0" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbLocalAddress}" />
<TextBox
x:Name="txtRequestHost9"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Watermark="Ipv4,Ipv6" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
Text="{x:Static resx:ResUI.TbMtu}" />
<TextBox
x:Name="txtShortId9"
Grid.Row="5"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Watermark="1500" />
Watermark="1280" />
</Grid>
<Grid
x:Name="gridAnytls"
@@ -850,7 +864,7 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="MTU" />
Text="{x:Static resx:ResUI.TbMtu}" />
<TextBox
x:Name="txtKcpMtu"
Grid.Row="1"
@@ -76,7 +76,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
cmbAlpn.IsEnabled = false;
break;
case EConfigType.TUIC:
@@ -85,7 +85,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
gridFinalmask.IsVisible = false;
cmbCongestionControl8.ItemsSource = Global.TuicCongestionControls;
@@ -116,11 +115,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbCoreType.IsEnabled = false;
gridFinalmask.IsVisible = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
cmbAlpn.IsEnabled = false;
cmbAlpn.SelectedValue = string.Empty;
cmbAllowInsecure.IsEnabled = false;
cmbAllowInsecure.SelectedValue = string.Empty;
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
break;
@@ -189,6 +185,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.WireGuard:
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgPresharedKey, v => v.txtPreSharedKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
@@ -37,8 +37,8 @@
<ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="0"
@@ -331,16 +331,36 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsBindInterface}" />
<TextBox
x:Name="txtbindInterface"
Grid.Row="21"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="21"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsBindInterfaceTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="22"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
<TextBox
x:Name="txtsendThrough"
Grid.Row="21"
Grid.Row="22"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
Watermark="0.0.0.0" />
<TextBlock
Grid.Row="21"
Grid.Row="22"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@@ -849,7 +869,7 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
Text="{x:Static resx:ResUI.TbMtu}" />
<ComboBox
x:Name="cmbMtu"
Grid.Row="5"
@@ -77,6 +77,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.bindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
@@ -192,6 +192,7 @@
Header="{x:Static resx:ResUI.menuExport2ShareUrl}"
InputGesture="Ctrl+C" />
<MenuItem x:Name="menuExport2ShareUrlBase64" Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
<MenuItem x:Name="menuExport2InnerUri" Header="{x:Static resx:ResUI.menuExport2InnerUri}" />
</MenuItem>
<Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupServer}">
@@ -88,6 +88,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2InnerUriCmd, v => v.menuExport2InnerUri).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()
+26 -9
View File
@@ -678,6 +678,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
@@ -721,14 +722,14 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbReserved}" />
Text="{x:Static resx:ResUI.TbPreSharedKey}" />
<TextBox
x:Name="txtPath9"
x:Name="txtPreSharedKey9"
Grid.Row="3"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="2,3,4"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
@@ -737,10 +738,26 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbReserved}" />
<TextBox
x:Name="txtPath9"
Grid.Row="4"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="0, 0, 0"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbLocalAddress}" />
<TextBox
x:Name="txtRequestHost9"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
@@ -748,20 +765,20 @@
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
Text="{x:Static resx:ResUI.TbMtu}" />
<TextBox
x:Name="txtShortId9"
Grid.Row="5"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
materialDesign:HintAssist.Hint="1500"
materialDesign:HintAssist.Hint="1280"
Style="{StaticResource DefTextBox}" />
</Grid>
<Grid
@@ -1119,7 +1136,7 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="MTU" />
Text="{x:Static resx:ResUI.TbMtu}" />
<TextBox
x:Name="txtKcpMtu"
Grid.Row="1"
+2 -5
View File
@@ -75,7 +75,7 @@ public partial class AddServerWindow
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
cmbAlpn.IsEnabled = false;
break;
case EConfigType.TUIC:
@@ -84,7 +84,6 @@ public partial class AddServerWindow
gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
gridFinalmask.Visibility = Visibility.Collapsed;
cmbCongestionControl8.ItemsSource = Global.TuicCongestionControls;
@@ -115,11 +114,8 @@ public partial class AddServerWindow
cmbCoreType.IsEnabled = false;
gridFinalmask.Visibility = Visibility.Collapsed;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
cmbAlpn.IsEnabled = false;
cmbAlpn.Text = string.Empty;
cmbAllowInsecure.IsEnabled = false;
cmbAllowInsecure.Text = string.Empty;
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
break;
@@ -188,6 +184,7 @@ public partial class AddServerWindow
case EConfigType.WireGuard:
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgPresharedKey, v => v.txtPreSharedKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
+30 -6
View File
@@ -65,6 +65,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -398,21 +399,44 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
Text="{x:Static resx:ResUI.TbSettingsBindInterface}" />
<TextBox
x:Name="txtsendThrough"
x:Name="txtbindInterface"
Grid.Row="21"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefTextBox}"
materialDesign:HintAssist.Hint="0.0.0.0" />
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="21"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsBindInterfaceTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="22"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
<TextBox
x:Name="txtsendThrough"
Grid.Row="22"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
materialDesign:HintAssist.Hint="0.0.0.0"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="22"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSendThroughTip}"
TextWrapping="Wrap" />
</Grid>
@@ -445,7 +469,7 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
Text="{x:Static resx:ResUI.TbMtu}" />
<TextBox Style="{StaticResource DefTextBox}"
x:Name="txtKcpmtu"
Grid.Row="1"
@@ -1109,7 +1133,7 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
Text="{x:Static resx:ResUI.TbMtu}" />
<ComboBox
x:Name="cmbMtu"
Grid.Row="5"
@@ -75,6 +75,7 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.bindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
+4
View File
@@ -242,6 +242,10 @@
x:Name="menuExport2ShareUrlBase64"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
<MenuItem
x:Name="menuExport2InnerUri"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuExport2InnerUri}" />
</MenuItem>
<Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupServer}">
+1
View File
@@ -82,6 +82,7 @@ public partial class ProfilesView
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2InnerUriCmd, v => v.menuExport2InnerUri).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()