using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.IO; using System.Text.Json; using System.Text.Json.Nodes; using PCL.Core.App; using PCL.Core.Logging; using PCL.Core.Utils; namespace PCL.Core.Link.EasyTier; // ReSharper disable InconsistentNaming, CompareOfFloatsByEqualityOperator public enum ETConnectionType { Local, P2P, Relay, Unknown } public class ETPlayerInfo { public required bool IsHost { get; init; } /// /// EasyTier 设置的原始主机名 /// public required string Hostname { get; init; } /// /// 显示的用户名,依次可能为自定义用户名、NAID 用户名 /// public string? Username { get; init; } public string? McName { get; init; } /// /// 连接方式 /// public ETConnectionType Cost { get; init; } = ETConnectionType.Unknown; /// /// 延迟 (ms) /// public double Ping { get; set; } /// /// 丢包率 (%) /// public double Loss { get; init; } public string? NatType { get; init; } /// /// 节点的 EasyTier 版本 /// public string? ETVersion { get; init; } } public static class ETInfoProvider { public const string ETNetworkNamePrefix = "PCLCELobby"; public const string ETNetworkSecretPrefix = "PCLCEETLOBBY2025"; public const string ETVersion = Scaffolding.EasyTier.EasyTierMetadata.CurrentEasyTierVer; public static readonly string ETPath = Path.Combine(Paths.SharedLocalData, "EasyTier", ETVersion, "easytier-windows-" + (RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x86_64")); private static ETConnectionType _GetConnectionType(string cost) { if (IsContains("p2p")) return ETConnectionType.P2P; if (IsContains("relay")) return ETConnectionType.Relay; if (IsContains("local")) return ETConnectionType.Local; return ETConnectionType.Unknown; bool IsContains(string str) => cost.Contains(str, StringComparison.InvariantCultureIgnoreCase); } private static readonly Process _CliProcess = new() { StartInfo = new ProcessStartInfo { FileName = $"{ETPath}\\easytier-cli.exe", WorkingDirectory = ETPath, Arguments= $"--rpc-portal 127.0.0.1:{ETController.ETRpcPort} -o json peer", ErrorDialog = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }, EnableRaisingEvents = true }; /// /// 检查 EasyTier 状态,若状态正常则返回 0。 /// /// public static async Task CheckETStatusAsync() { var retryCount = 0; var process = ETController.ETProcess; while (process is null && retryCount < 10) { await Task.Delay(1000); retryCount++; } if (process is not null) { while (ETController.Status != ETState.Ready) { var info = GetPlayerList().Item1?[0]; if (info is null) { await Task.Delay(1000); continue; } if (info.Ping != 1000) { ETController.Status = ETState.Ready; } await Task.Delay(1000); } return 0; } return 1; } /// /// 获取 EasyTier 网络的成员列表和本地信息,若获取失败则均返回 null。 /// /// Tuple(玩家列表, 本地信息) public static Tuple?, ETPlayerInfo?> GetPlayerList() { try { _CliProcess.StartInfo.Arguments = $"--rpc-portal 127.0.0.1:{ETController.ETRpcPort} -o json peer"; _CliProcess.Start(); _CliProcess.WaitForExit(180); var output = _CliProcess.StandardOutput.ReadToEnd() + _CliProcess.StandardError.ReadToEnd(); if (!_CliProcess.HasExited) { LogWrapper.Warn("Link", "Cli 获取结果超时(180 ms),程序状态可能异常!"); LogWrapper.Warn("Link", "获取到 EasyTier Cli 信息: \r\n" + output); } var playerList = new List(); ETPlayerInfo? localInfo = null; if (JsonCompat.ParseNode(output) is not JsonArray json) return new Tuple?, ETPlayerInfo?>(null, null); foreach (var p in json) { var info = p.Deserialize(JsonCompat.SerializerOptions); if (info is null) { continue; } if (info.Hostname.StartsWith("PublicServer")) { continue; } // 服务器 var hostnameSplit = info.Hostname.Split('|'); var playerInfo = new ETPlayerInfo { IsHost = info.Hostname.StartsWith("H|") || info.Ipv4 == "10.144.144.1", Hostname = info.Hostname, Username = hostnameSplit.Length >= 2 ? hostnameSplit[1] : null, McName = hostnameSplit.Length == 3 ? hostnameSplit[2] : null, Cost = _GetConnectionType(info.Cost), Ping = Math.Round(Convert.ToDouble(info.Ping != "-" ? info.Ping : "0")), Loss = Math.Round(Convert.ToDouble(info.Loss != "-" ? info.Loss.Replace("%", "") : "0")), NatType = info.NatType, ETVersion = info.ETVersion }; if (playerInfo.IsHost) { playerList.Insert(0, playerInfo); // 主机信息放在列表首位 } else { playerList.Add(playerInfo); } if (playerInfo.Cost == ETConnectionType.Local) { localInfo = playerInfo; } } return new Tuple?, ETPlayerInfo?>(playerList, localInfo); } catch (Exception ex) { LogWrapper.Error(ex,"Link", "获取 EasyTier 网络成员列表失败"); return new Tuple?, ETPlayerInfo?>(null, null); } } }