mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-04 12:48:10 +03:00
740 lines
26 KiB
C#
740 lines
26 KiB
C#
using v2rayN.Base;
|
|
using v2rayN.Mode;
|
|
using v2rayN.Resx;
|
|
|
|
namespace v2rayN.Handler
|
|
{
|
|
internal class CoreConfigSingbox
|
|
{
|
|
private string SampleClient = Global.SingboxSampleClient;
|
|
private Config _config;
|
|
|
|
public CoreConfigSingbox(Config config)
|
|
{
|
|
_config = config;
|
|
}
|
|
|
|
public int GenerateClientConfigContent(ProfileItem node, out SingboxConfig? singboxConfig, out string msg)
|
|
{
|
|
singboxConfig = null;
|
|
try
|
|
{
|
|
if (node == null
|
|
|| node.port <= 0)
|
|
{
|
|
msg = ResUI.CheckServerSettings;
|
|
return -1;
|
|
}
|
|
|
|
msg = ResUI.InitialConfiguration;
|
|
|
|
string result = Utils.GetEmbedText(SampleClient);
|
|
if (Utils.IsNullOrEmpty(result))
|
|
{
|
|
msg = ResUI.FailedGetDefaultConfiguration;
|
|
return -1;
|
|
}
|
|
|
|
singboxConfig = Utils.FromJson<SingboxConfig>(result);
|
|
if (singboxConfig == null)
|
|
{
|
|
msg = ResUI.FailedGenDefaultConfiguration;
|
|
return -1;
|
|
}
|
|
|
|
log(singboxConfig);
|
|
|
|
inbound(singboxConfig);
|
|
|
|
outbound(node, singboxConfig);
|
|
|
|
routing(singboxConfig);
|
|
|
|
dns(node, singboxConfig);
|
|
|
|
statistic(singboxConfig);
|
|
|
|
msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog("GenerateClientConfig4Singbox", ex);
|
|
msg = ResUI.FailedGenDefaultConfiguration;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private int log(SingboxConfig singboxConfig)
|
|
{
|
|
try
|
|
{
|
|
switch (_config.coreBasicItem.loglevel)
|
|
{
|
|
case "debug":
|
|
case "info":
|
|
case "error":
|
|
singboxConfig.log.level = _config.coreBasicItem.loglevel;
|
|
break;
|
|
|
|
case "warning":
|
|
singboxConfig.log.level = "warn";
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (_config.coreBasicItem.loglevel == "none")
|
|
{
|
|
singboxConfig.log.disabled = true;
|
|
}
|
|
if (_config.coreBasicItem.logEnabled)
|
|
{
|
|
var dtNow = DateTime.Now;
|
|
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#region inbound private
|
|
|
|
private int inbound(SingboxConfig singboxConfig)
|
|
{
|
|
try
|
|
{
|
|
singboxConfig.inbounds.Clear();
|
|
|
|
if (!_config.tunModeItem.enableTun || (_config.tunModeItem.enableTun && _config.tunModeItem.enableExInbound))
|
|
{
|
|
var inbound = new Inbound4Sbox()
|
|
{
|
|
type = Global.InboundSocks,
|
|
tag = Global.InboundSocks,
|
|
listen = Global.Loopback,
|
|
};
|
|
singboxConfig.inbounds.Add(inbound);
|
|
|
|
inbound.listen_port = LazyConfig.Instance.GetLocalPort(Global.InboundSocks);
|
|
inbound.sniff = _config.inbound[0].sniffingEnabled;
|
|
inbound.sniff_override_destination = _config.inbound[0].routeOnly ? false : _config.inbound[0].sniffingEnabled;
|
|
inbound.domain_strategy = Utils.IsNullOrEmpty(_config.routingBasicItem.domainStrategy4Singbox) ? null : _config.routingBasicItem.domainStrategy4Singbox;
|
|
|
|
if (_config.routingBasicItem.enableRoutingAdvanced)
|
|
{
|
|
var routing = ConfigHandler.GetDefaultRouting(ref _config);
|
|
if (!Utils.IsNullOrEmpty(routing.domainStrategy4Singbox))
|
|
{
|
|
inbound.domain_strategy = routing.domainStrategy4Singbox;
|
|
}
|
|
}
|
|
|
|
//http
|
|
var inbound2 = GetInbound(inbound, Global.InboundHttp, 1, false);
|
|
singboxConfig.inbounds.Add(inbound2);
|
|
|
|
if (_config.inbound[0].allowLANConn)
|
|
{
|
|
if (_config.inbound[0].newPort4LAN)
|
|
{
|
|
var inbound3 = GetInbound(inbound, Global.InboundSocks2, 2, true);
|
|
inbound3.listen = "::";
|
|
singboxConfig.inbounds.Add(inbound3);
|
|
|
|
var inbound4 = GetInbound(inbound, Global.InboundHttp2, 3, false);
|
|
inbound4.listen = "::";
|
|
singboxConfig.inbounds.Add(inbound4);
|
|
|
|
//auth
|
|
if (!Utils.IsNullOrEmpty(_config.inbound[0].user) && !Utils.IsNullOrEmpty(_config.inbound[0].pass))
|
|
{
|
|
inbound3.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } };
|
|
inbound4.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } };
|
|
}
|
|
}
|
|
else
|
|
{
|
|
inbound.listen = "::";
|
|
inbound2.listen = "::";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_config.tunModeItem.enableTun)
|
|
{
|
|
if (_config.tunModeItem.mtu <= 0)
|
|
{
|
|
_config.tunModeItem.mtu = Convert.ToInt32(Global.TunMtus[0]);
|
|
}
|
|
if (Utils.IsNullOrEmpty(_config.tunModeItem.stack))
|
|
{
|
|
_config.tunModeItem.stack = Global.TunStacks[0];
|
|
}
|
|
|
|
var tunInbound = Utils.FromJson<Inbound4Sbox>(Utils.GetEmbedText(Global.TunSingboxInboundFileName));
|
|
tunInbound.mtu = _config.tunModeItem.mtu;
|
|
tunInbound.strict_route = _config.tunModeItem.strictRoute;
|
|
tunInbound.stack = _config.tunModeItem.stack;
|
|
|
|
singboxConfig.inbounds.Add(tunInbound);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private Inbound4Sbox? GetInbound(Inbound4Sbox inItem, string tag, int offset, bool bSocks)
|
|
{
|
|
var inbound = Utils.DeepCopy(inItem);
|
|
inbound.tag = tag;
|
|
inbound.listen_port = inItem.listen_port + offset;
|
|
inbound.type = bSocks ? Global.InboundSocks : Global.InboundHttp;
|
|
return inbound;
|
|
}
|
|
|
|
#endregion inbound private
|
|
|
|
#region outbound private
|
|
|
|
private int outbound(ProfileItem node, SingboxConfig singboxConfig)
|
|
{
|
|
try
|
|
{
|
|
if (_config.tunModeItem.enableTun)
|
|
{
|
|
singboxConfig.outbounds.Add(new()
|
|
{
|
|
type = "dns",
|
|
tag = "dns_out"
|
|
});
|
|
}
|
|
|
|
var outbound = singboxConfig.outbounds[0];
|
|
outbound.server = node.address;
|
|
outbound.server_port = node.port;
|
|
|
|
if (node.configType == EConfigType.VMess)
|
|
{
|
|
outbound.type = Global.vmessProtocolLite;
|
|
|
|
outbound.uuid = node.id;
|
|
outbound.alter_id = node.alterId;
|
|
if (Global.vmessSecuritys.Contains(node.security))
|
|
{
|
|
outbound.security = node.security;
|
|
}
|
|
else
|
|
{
|
|
outbound.security = Global.DefaultSecurity;
|
|
}
|
|
|
|
outboundMux(node, outbound);
|
|
}
|
|
else if (node.configType == EConfigType.Shadowsocks)
|
|
{
|
|
outbound.type = Global.ssProtocolLite;
|
|
|
|
outbound.method = LazyConfig.Instance.GetShadowsocksSecuritys(node).Contains(node.security) ? node.security : "none";
|
|
outbound.password = node.id;
|
|
|
|
outboundMux(node, outbound);
|
|
}
|
|
else if (node.configType == EConfigType.Socks)
|
|
{
|
|
outbound.type = Global.socksProtocolLite;
|
|
|
|
outbound.version = "5";
|
|
if (!Utils.IsNullOrEmpty(node.security)
|
|
&& !Utils.IsNullOrEmpty(node.id))
|
|
{
|
|
outbound.username = node.security;
|
|
outbound.password = node.id;
|
|
}
|
|
}
|
|
else if (node.configType == EConfigType.VLESS)
|
|
{
|
|
outbound.type = Global.vlessProtocolLite;
|
|
|
|
outbound.uuid = node.id;
|
|
|
|
outbound.packet_encoding = "xudp";
|
|
|
|
if (Utils.IsNullOrEmpty(node.flow))
|
|
{
|
|
outboundMux(node, outbound);
|
|
}
|
|
else
|
|
{
|
|
outbound.flow = node.flow;
|
|
}
|
|
}
|
|
else if (node.configType == EConfigType.Trojan)
|
|
{
|
|
outbound.type = Global.trojanProtocolLite;
|
|
|
|
outbound.password = node.id;
|
|
|
|
outboundMux(node, outbound);
|
|
}
|
|
|
|
outboundTls(node, outbound);
|
|
|
|
outboundTransport(node, outbound);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private int outboundMux(ProfileItem node, Outbound4Sbox outbound)
|
|
{
|
|
try
|
|
{
|
|
//if (_config.coreBasicItem.muxEnabled)
|
|
//{
|
|
// var mux = new Multiplex4Sbox()
|
|
// {
|
|
// enabled = true,
|
|
// protocol = _config.mux4Sbox.protocol,
|
|
// max_connections = _config.mux4Sbox.max_connections,
|
|
// min_streams = _config.mux4Sbox.min_streams,
|
|
// max_streams = _config.mux4Sbox.max_streams,
|
|
// padding = _config.mux4Sbox.padding
|
|
// };
|
|
// outbound.multiplex = mux;
|
|
//}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private int outboundTls(ProfileItem node, Outbound4Sbox outbound)
|
|
{
|
|
try
|
|
{
|
|
if (node.streamSecurity == Global.StreamSecurityReality || node.streamSecurity == Global.StreamSecurity)
|
|
{
|
|
var server_name = string.Empty;
|
|
if (!string.IsNullOrWhiteSpace(node.sni))
|
|
{
|
|
server_name = node.sni;
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(node.requestHost))
|
|
{
|
|
server_name = Utils.String2List(node.requestHost)[0];
|
|
}
|
|
var tls = new Tls4Sbox()
|
|
{
|
|
enabled = true,
|
|
server_name = server_name,
|
|
insecure = Utils.ToBool(node.allowInsecure.IsNullOrEmpty() ? _config.coreBasicItem.defAllowInsecure.ToString().ToLower() : node.allowInsecure),
|
|
alpn = node.GetAlpn(),
|
|
};
|
|
if (!Utils.IsNullOrEmpty(node.fingerprint))
|
|
{
|
|
tls.utls = new Utls4Sbox()
|
|
{
|
|
enabled = true,
|
|
fingerprint = node.fingerprint.IsNullOrEmpty() ? _config.coreBasicItem.defFingerprint : node.fingerprint
|
|
};
|
|
}
|
|
if (node.streamSecurity == Global.StreamSecurityReality)
|
|
{
|
|
tls.reality = new Reality4Sbox()
|
|
{
|
|
enabled = true,
|
|
public_key = node.publicKey,
|
|
short_id = node.shortId
|
|
};
|
|
tls.insecure = false;
|
|
}
|
|
outbound.tls = tls;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private int outboundTransport(ProfileItem node, Outbound4Sbox outbound)
|
|
{
|
|
try
|
|
{
|
|
var transport = new Transport4Sbox();
|
|
|
|
switch (node.GetNetwork())
|
|
{
|
|
case "h2":
|
|
transport.type = "http";
|
|
transport.host = Utils.IsNullOrEmpty(node.requestHost) ? null : Utils.String2List(node.requestHost);
|
|
transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path;
|
|
break;
|
|
|
|
case "ws":
|
|
transport.type = "ws";
|
|
transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path;
|
|
if (!Utils.IsNullOrEmpty(node.requestHost))
|
|
{
|
|
transport.headers = new()
|
|
{
|
|
Host = node.requestHost
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "quic":
|
|
transport.type = "quic";
|
|
break;
|
|
|
|
case "grpc":
|
|
transport.type = "grpc";
|
|
transport.service_name = node.path;
|
|
transport.idle_timeout = _config.grpcItem.idle_timeout.ToString("##s");
|
|
transport.ping_timeout = _config.grpcItem.health_check_timeout.ToString("##s");
|
|
transport.permit_without_stream = _config.grpcItem.permit_without_stream;
|
|
break;
|
|
|
|
default:
|
|
transport = null;
|
|
break;
|
|
}
|
|
|
|
outbound.transport = transport;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endregion outbound private
|
|
|
|
#region routing rule private
|
|
|
|
private int routing(SingboxConfig singboxConfig)
|
|
{
|
|
try
|
|
{
|
|
if (_config.tunModeItem.enableTun)
|
|
{
|
|
singboxConfig.route.auto_detect_interface = true;
|
|
|
|
var tunRules = Utils.FromJson<List<Rule4Sbox>>(Utils.GetEmbedText(Global.TunSingboxRulesFileName));
|
|
singboxConfig.route.rules.AddRange(tunRules);
|
|
|
|
routingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe);
|
|
singboxConfig.route.rules.Add(new()
|
|
{
|
|
port = new() { 53 },
|
|
outbound = "dns_out",
|
|
process_name = lstDnsExe
|
|
});
|
|
|
|
singboxConfig.route.rules.Add(new()
|
|
{
|
|
outbound = "direct",
|
|
process_name = lstDirectExe
|
|
});
|
|
}
|
|
|
|
if (_config.routingBasicItem.enableRoutingAdvanced)
|
|
{
|
|
var routing = ConfigHandler.GetDefaultRouting(ref _config);
|
|
if (routing != null)
|
|
{
|
|
var rules = Utils.FromJson<List<RulesItem>>(routing.ruleSet);
|
|
foreach (var item in rules!)
|
|
{
|
|
if (item.enabled)
|
|
{
|
|
routingUserRule(item, singboxConfig.route.rules);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var lockedItem = ConfigHandler.GetLockedRoutingItem(ref _config);
|
|
if (lockedItem != null)
|
|
{
|
|
var rules = Utils.FromJson<List<RulesItem>>(lockedItem.ruleSet);
|
|
foreach (var item in rules!)
|
|
{
|
|
routingUserRule(item, singboxConfig.route.rules);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private void routingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
|
|
{
|
|
lstDnsExe = new();
|
|
lstDirectExe = new();
|
|
var coreInfos = LazyConfig.Instance.GetCoreInfos();
|
|
foreach (var it in coreInfos)
|
|
{
|
|
if (it.coreType == ECoreType.v2rayN)
|
|
{
|
|
continue;
|
|
}
|
|
foreach (var it2 in it.coreExes)
|
|
{
|
|
if (!lstDnsExe.Contains(it2) && it.coreType != ECoreType.sing_box)
|
|
{
|
|
lstDnsExe.Add($"{it2}.exe");
|
|
}
|
|
|
|
if (!lstDirectExe.Contains(it2))
|
|
{
|
|
lstDirectExe.Add($"{it2}.exe");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private int routingUserRule(RulesItem item, List<Rule4Sbox> rules)
|
|
{
|
|
try
|
|
{
|
|
if (item == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var rule = new Rule4Sbox()
|
|
{
|
|
outbound = item.outboundTag,
|
|
};
|
|
|
|
if (!Utils.IsNullOrEmpty(item.port))
|
|
{
|
|
if (item.port.Contains("-"))
|
|
{
|
|
rule.port_range = new List<string> { item.port.Replace("-", ":") };
|
|
}
|
|
else
|
|
{
|
|
rule.port = new List<int> { Utils.ToInt(item.port) };
|
|
}
|
|
}
|
|
if (item.protocol?.Count > 0)
|
|
{
|
|
rule.protocol = item.protocol;
|
|
}
|
|
if (item.inboundTag?.Count >= 0)
|
|
{
|
|
rule.inbound = item.inboundTag;
|
|
}
|
|
var rule2 = Utils.DeepCopy(rule);
|
|
var rule3 = Utils.DeepCopy(rule);
|
|
|
|
var hasDomainIp = false;
|
|
if (item.domain?.Count > 0)
|
|
{
|
|
foreach (var it in item.domain)
|
|
{
|
|
parseV2Domain(it, rule);
|
|
}
|
|
rules.Add(rule);
|
|
hasDomainIp = true;
|
|
}
|
|
|
|
if (item.ip?.Count > 0)
|
|
{
|
|
foreach (var it in item.ip)
|
|
{
|
|
parseV2Address(it, rule2);
|
|
}
|
|
rules.Add(rule2);
|
|
hasDomainIp = true;
|
|
}
|
|
|
|
if (_config.tunModeItem.enableTun && item.process?.Count > 0)
|
|
{
|
|
rule3.process_name = item.process;
|
|
rules.Add(rule3);
|
|
hasDomainIp = true;
|
|
}
|
|
|
|
if (!hasDomainIp)
|
|
{
|
|
rules.Add(rule);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private void parseV2Domain(string domain, Rule4Sbox rule)
|
|
{
|
|
if (domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
|
|
{
|
|
return;
|
|
}
|
|
else if (domain.StartsWith("geosite:"))
|
|
{
|
|
if (rule.geosite is null) { rule.geosite = new(); }
|
|
rule.geosite?.Add(domain.Substring(8));
|
|
}
|
|
else if (domain.StartsWith("regexp:"))
|
|
{
|
|
if (rule.domain_regex is null) { rule.domain_regex = new(); }
|
|
rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7));
|
|
}
|
|
else if (domain.StartsWith("domain:"))
|
|
{
|
|
if (rule.domain is null) { rule.domain = new(); }
|
|
if (rule.domain_suffix is null) { rule.domain_suffix = new(); }
|
|
rule.domain?.Add(domain.Substring(7));
|
|
rule.domain_suffix?.Add("." + domain.Substring(7));
|
|
}
|
|
else if (domain.StartsWith("full:"))
|
|
{
|
|
if (rule.domain is null) { rule.domain = new(); }
|
|
rule.domain?.Add(domain.Substring(5));
|
|
}
|
|
else if (domain.StartsWith("keyword:"))
|
|
{
|
|
if (rule.domain_keyword is null) { rule.domain_keyword = new(); }
|
|
rule.domain_keyword?.Add(domain.Substring(8));
|
|
}
|
|
else
|
|
{
|
|
if (rule.domain_keyword is null) { rule.domain_keyword = new(); }
|
|
rule.domain_keyword?.Add(domain);
|
|
}
|
|
}
|
|
|
|
private void parseV2Address(string address, Rule4Sbox rule)
|
|
{
|
|
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
|
|
{
|
|
return;
|
|
}
|
|
else if (address.StartsWith("geoip:!"))
|
|
{
|
|
return;
|
|
}
|
|
else if (address.StartsWith("geoip:"))
|
|
{
|
|
if (rule.geoip is null) { rule.geoip = new(); }
|
|
rule.geoip?.Add(address.Substring(6));
|
|
}
|
|
else
|
|
{
|
|
if (rule.ip_cidr is null) { rule.ip_cidr = new(); }
|
|
rule.ip_cidr?.Add(address);
|
|
}
|
|
}
|
|
|
|
#endregion routing rule private
|
|
|
|
#region dns private
|
|
|
|
private int dns(ProfileItem node, SingboxConfig singboxConfig)
|
|
{
|
|
try
|
|
{
|
|
Dns4Sbox? dns4Sbox;
|
|
if (_config.tunModeItem.enableTun)
|
|
{
|
|
var item = LazyConfig.Instance.GetDNSItem(ECoreType.sing_box);
|
|
var tunDNS = item?.tunDNS;
|
|
if (string.IsNullOrWhiteSpace(tunDNS))
|
|
{
|
|
tunDNS = Utils.GetEmbedText(Global.TunSingboxDNSFileName);
|
|
}
|
|
dns4Sbox = Utils.FromJson<Dns4Sbox>(tunDNS);
|
|
}
|
|
else
|
|
{
|
|
var item = LazyConfig.Instance.GetDNSItem(ECoreType.sing_box);
|
|
var normalDNS = item?.normalDNS;
|
|
if (string.IsNullOrWhiteSpace(normalDNS))
|
|
{
|
|
normalDNS = "{\"servers\":[{\"address\":\"tcp://8.8.8.8\"}]}";
|
|
}
|
|
|
|
dns4Sbox = Utils.FromJson<Dns4Sbox>(normalDNS);
|
|
}
|
|
if (dns4Sbox is null)
|
|
{
|
|
return 0;
|
|
}
|
|
//Add the dns of the remote server domain
|
|
if (dns4Sbox.rules is null)
|
|
{
|
|
dns4Sbox.rules = new();
|
|
}
|
|
dns4Sbox.servers.Add(new()
|
|
{
|
|
tag = "local_local",
|
|
address = "223.5.5.5",
|
|
detour = "direct"
|
|
});
|
|
dns4Sbox.rules.Add(new()
|
|
{
|
|
server = "local_local",
|
|
outbound = "any"
|
|
});
|
|
|
|
singboxConfig.dns = dns4Sbox;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Utils.SaveLog(ex.Message, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endregion dns private
|
|
|
|
private int statistic(SingboxConfig singboxConfig)
|
|
{
|
|
if (_config.guiItem.enableStatistics)
|
|
{
|
|
singboxConfig.experimental = new Experimental4Sbox()
|
|
{
|
|
//v2ray_api = new V2ray_Api4Sbox()
|
|
//{
|
|
// listen = $"{Global.Loopback}:{Global.statePort}",
|
|
// stats = new Stats4Sbox()
|
|
// {
|
|
// enabled = true,
|
|
// }
|
|
//}
|
|
clash_api = new Clash_Api4Sbox()
|
|
{
|
|
external_controller = $"{Global.Loopback}:{Global.statePort}",
|
|
store_selected = true
|
|
}
|
|
};
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
} |