using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace ServiceLib.Services.CoreConfig;
///
/// Core configuration file processing class.
/// The TUN state is taken as a snapshot so the generated config always agrees
/// with the launch elevation decision (see CoreManager.ShouldRunAsSudo).
///
public class CoreConfigClashService(Config config, bool isTunEnabled)
{
private static readonly string _tag = "CoreConfigClashService";
public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
try
{
if (node == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.Delete(fileName);
}
var addressFileName = node.Address;
if (addressFileName.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
if (!File.Exists(addressFileName))
{
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "1";
return ret;
}
var tagYamlStr1 = "!";
var tagYamlStr2 = "__strn__";
var tagYamlStr3 = "!!str";
var txtFile = await File.ReadAllTextAsync(addressFileName);
txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2);
//YAML anchors
if (txtFile.Contains("<<:") && txtFile.Contains('*') && txtFile.Contains('&'))
{
txtFile = YamlUtils.PreprocessYaml(txtFile);
}
var fileContent = YamlUtils.FromYaml>(txtFile);
if (fileContent == null)
{
ret.Msg = ResUI.FailedConversionConfiguration;
return ret;
}
//mixed-port
fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
//log-level
fileContent["log-level"] = GetLogLevel(config.CoreBasicItem.Loglevel);
//external-controller
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
fileContent.Remove("secret");
//allow-lan
if (config.Inbound.First().AllowLANConn)
{
fileContent["allow-lan"] = "true";
fileContent["bind-address"] = "*";
}
else
{
fileContent["allow-lan"] = "false";
}
//ipv6
fileContent["ipv6"] = config.ClashUIItem.EnableIPv6;
//mode
if (!fileContent.ContainsKey("mode"))
{
fileContent["mode"] = nameof(ERuleMode.Rule).ToLower();
}
else
{
if (config.ClashUIItem.RuleMode != ERuleMode.Unchanged)
{
fileContent["mode"] = config.ClashUIItem.RuleMode.ToString().ToLower();
}
}
//enable tun mode
if (isTunEnabled)
{
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
if (tun.IsNotEmpty())
{
var tunContent = YamlUtils.FromYaml>(tun);
if (tunContent != null)
{
fileContent["tun"] = tunContent["tun"];
}
}
}
//Mixin
try
{
await MixinContent(fileContent, node);
}
catch (Exception ex)
{
Logging.SaveLog($"{_tag}-Mixin", ex);
}
// Mihomo parses plain values such as 815458e4 as floats, so quote REALITY short IDs.
var originalRealityShortIds = new List<(Dictionary