using System; using System.Diagnostics; using System.IO; using System.Linq; using PCL.Core.App; using PCL.Core.App.Localization; using PCL.Core.IO.Net; using PCL.Core.Logging; using PCL.Core.Utils; using PCL.Core.Utils.Secret; using static PCL.Core.Link.EasyTier.ETInfoProvider; using static PCL.Core.Link.Lobby.LobbyInfoProvider; using static PCL.Core.Link.Natayark.NatayarkProfileManager; namespace PCL.Core.Link.EasyTier; // ReSharper disable InconsistentNaming public enum ETState { Stopped, Running, Ready } public static class ETController { public static Process? ETProcess { get; private set; } public static int ETRpcPort { get; private set; } public static ETState Status { get; internal set; } public static int Precheck() { var existedET = Process.GetProcessesByName("easytier-core"); foreach (var p in existedET) { LogWrapper.Warn("Link", $"发现已有的 EasyTier 实例,可能影响与启动器所用的实例通信: {p.Id}"); } // 检查文件 LogWrapper.Info("Link", "EasyTier 路径: " + ETPath); if (!(File.Exists(ETPath + "\\easytier-core.exe") && File.Exists(ETPath + "\\easytier-cli.exe") && File.Exists(ETPath + "\\Packet.dll"))) { LogWrapper.Error("Link", "EasyTier 不存在或不完整"); return 1; } return 0; } public static int Launch(bool isHost, string? hostname = null) { try { if (TargetLobby is null || Precheck() != 0) { return 1; } ETProcess = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo { FileName = $"{ETPath}\\easytier-core.exe", WorkingDirectory = ETPath, WindowStyle = ProcessWindowStyle.Hidden } }; var arguments = new ArgumentsBuilder(); // 大厅信息 var name = TargetLobby.NetworkName; var secret = TargetLobby.NetworkSecret; switch (TargetLobby.Type) { case LobbyType.PCLCE: name = ETNetworkNamePrefix + name; secret = ETNetworkSecretPrefix + secret; break; case LobbyType.Terracotta: name = "terracotta-mc-" + name; break; default: throw new NotSupportedException(Lang.Text("Link.Lobby.UnsupportedType", TargetLobby.Type)); } arguments.AddFlag("no-tun"); arguments.Add("network-name", name); arguments.Add("network-secret", secret); arguments.Add("relay-network-whitelist", name); arguments.Add("private-mode", "true"); // 网络参数 if (isHost) { LogWrapper.Info("Link", $"本机作为创建者创建大厅,EasyTier 网络名称: {name}"); arguments.Add("i", "10.114.51.41"); arguments.Add("tcp-whitelist", TargetLobby.Port.ToString()); arguments.Add("udp-whitelist", TargetLobby.Port.ToString()); } else { LogWrapper.Info("Link", $"本机作为加入者加入大厅,EasyTier 网络名称: {name}"); arguments.AddFlag("d"); arguments.Add("tcp-whitelist", "0"); arguments.Add("udp-whitelist", "0"); JoinerLocalPort = NetworkHelper.NewTcpPort(); LogWrapper.Info("Link", $"ET 端口转发: 远程 {TargetLobby.Port} -> 本地 {JoinerLocalPort}"); arguments.Add("port-forward", $"tcp://127.0.0.1:{JoinerLocalPort}/{TargetLobby.Ip}:{TargetLobby.Port}"); arguments.Add("port-forward", $"udp://127.0.0.1:{JoinerLocalPort}/{TargetLobby.Ip}:{TargetLobby.Port}"); } // 节点设置 var relays = ETRelay.RelayList; var customNodes = Config.Link.CustomRelayServer; foreach (var node in customNodes.Split([';'], StringSplitOptions.RemoveEmptyEntries)) { if (node.Contains("tcp://") || node.Contains("udp://")) { relays.Add(new ETRelay { Url = node, Name = "Custom", Type = ETRelayType.Custom }); } else { LogWrapper.Warn("Link", $"无效的自定义节点 URL: {node}"); } } foreach (var relay in from relay in relays let serverType = Config.Link.ServerType where (relay.Type == ETRelayType.Selfhosted && serverType != 2) || (relay.Type == ETRelayType.Community && serverType == 1) || relay.Type == ETRelayType.Custom select relay) { arguments.Add("p", relay.Url); } // 中继行为设置 if (Config.Link.RelayType == LinkRelayBehavior.ForceRelay) { arguments.AddFlag("disable-p2p"); } // 数据流代理设置 arguments.AddFlag("enable-quic-proxy"); arguments.AddFlag("enable-kcp-proxy"); arguments.AddFlag("use-smoltcp"); arguments.Add("encryption-algorithm", "chacha20"); arguments.Add("default-protocol", Config.Link.ProtocolPreference.ToString().ToLower()); arguments.AddFlagIf(!Config.Link.TryPunchSym, "disable-sym-hole-punching"); arguments.AddFlagIf(!Config.Link.EnableIPv6, "disable-ipv6"); // 用户名与其他参数 arguments.AddFlagIf(Config.Link.UseLatencyFirstMode, "latency-first"); arguments.Add("compression", "zstd"); arguments.AddFlag("multi-thread"); arguments.Add("machine-id", Identify.LauncherId); // TODO: 等待玩家档案迁移以获取正在使用的档案名称 var showName = "default"; if (AllowCustomName && !string.IsNullOrWhiteSpace(Config.Link.Username)) { showName = Config.Link.Username; } else if (!string.IsNullOrWhiteSpace(NaidProfile.Username)) { showName = NaidProfile.Username; } arguments.Add("hostname", (isHost ? "H|" : "J|") + showName + (!string.IsNullOrWhiteSpace(hostname) ? "|" + hostname : "")); // 指定 RPC 端口以避免与其他 ET 实例冲突 ETRpcPort = NetworkHelper.NewTcpPort(); arguments.Add("rpc-portal", $"127.0.0.1:{ETRpcPort}"); // 启动 ETProcess.StartInfo.Arguments = arguments.GetResult(); LogWrapper.Info("Link", "启动 EasyTier"); // 操作 UI 显示大厅编号(可能写到 XAML 下面 UI 控制那部分去?) ETProcess.Start(); Status = ETState.Running; return 0; } catch (Exception ex) { LogWrapper.Error(ex, "Link", "尝试启动 EasyTier 时遇到问题"); Status = ETState.Stopped; ETProcess = null; return 1; } } public static void Exit() { if (Status == ETState.Stopped || ETProcess is null) return; try { LogWrapper.Info("Link", $"关闭 EasyTier (PID: {ETProcess.Id})"); ETProcess.Kill(); ETProcess.WaitForExit(200); } catch (InvalidOperationException) { LogWrapper.Warn("Link", "EasyTier 进程不存在,可能已退出"); } catch (NullReferenceException) { LogWrapper.Warn("Link", "EasyTier 进程不存在,可能已退出"); } catch (Exception ex) { LogWrapper.Error(ex, "Link", "关闭 EasyTier 时遇到问题"); } finally { Status = ETState.Stopped; ETProcess = null; } } }