diff --git a/HostUtilities/HostUtilities/UI_DisplayLatency.cs b/HostUtilities/HostUtilities/UI_DisplayLatency.cs index 078cd01..b10f3ee 100644 --- a/HostUtilities/HostUtilities/UI_DisplayLatency.cs +++ b/HostUtilities/HostUtilities/UI_DisplayLatency.cs @@ -10,7 +10,9 @@ using System.Linq; using static HostUtilities.KickUser; using Steamworks; - +using BitStream; +using System.Text; +using Team17.Online.Multiplayer.Messaging; namespace HostUtilities { public class UI_DisplayLatency @@ -18,17 +20,23 @@ public class UI_DisplayLatency public static void Log(string mes) => MODEntry.LogInfo(MethodBase.GetCurrentMethod().DeclaringType.Name, mes); public static void LogE(string mes) => MODEntry.LogError(MethodBase.GetCurrentMethod().DeclaringType.Name, mes); public static void LogW(string mes) => MODEntry.LogWarning(MethodBase.GetCurrentMethod().DeclaringType.Name, mes); - + public static List UserConnections { get; set; } = new List(); public static Harmony HarmonyInstance { get; set; } private static MyOnScreenDebugDisplay onScreenDebugDisplay; private static NetworkStateDebugDisplay NetworkDebugUI = null; public static ConfigEntry ShowEnabled; + public static ConfigEntry ShowEnabledhost; public static ConfigEntry isShowDebugInfo; public static bool canAdd; + public const MessageType UsersMessageType = (MessageType)69; + private const float BroadcastIntervalSeconds = 1.0f; // 广播间隔时间,每隔1秒进行一次广播 + private static float broadcastTimer = 0.0f; + private static string serverFriendsMessage = String.Empty; public static void Awake() { - ShowEnabled = MODEntry.Instance.Config.Bind("00-UI", "03-屏幕右上角显示延迟", true); + ShowEnabled = MODEntry.Instance.Config.Bind("00-UI", "03-01-屏幕右上角显示延迟", true); + ShowEnabledhost = MODEntry.Instance.Config.Bind("00-UI", "03-02-显示主机视角延迟(主机开启才会显示)", false); isShowDebugInfo = MODEntry.Instance.Config.Bind("00-UI", "04-屏幕右上角增加显示调试信息", false); canAdd = false; onScreenDebugDisplay = new MyOnScreenDebugDisplay(); @@ -40,6 +48,7 @@ public static void Awake() public static void Update() { + onScreenDebugDisplay.Update(); if (NetworkDebugUI != null && !ShowEnabled.Value) @@ -50,6 +59,64 @@ public static void Update() { AddNetworkDebugUI(); } + if (ShowEnabledhost.Value && !ShowEnabled.Value) + { + ShowEnabledhost.Value = false; + } + broadcastTimer += Time.deltaTime; + if (broadcastTimer >= BroadcastIntervalSeconds) + { + if (MODEntry.isHost && ShowEnabledhost.Value) + { + UserConnections.Add(new UserConnectionInfo("主机", 0, (CSteamID)MODEntry.CurrentSteamID.m_SteamID)); + try + { + MultiplayerController multiplayerController = GameUtils.RequestManager(); + Server server = multiplayerController.m_LocalServer; + Dictionary remoteClientConnectionsDict = server.m_RemoteClientConnections; + + if (server != null) + { + int index = 2; + foreach (User user in ServerUserSystem.m_Users._items.Skip(1)) + { + foreach (var kvp in remoteClientConnectionsDict) + { + IOnlineMultiplayerSessionUserId sessionUserId = kvp.Key; + NetworkConnection connection = kvp.Value; + if (user.DisplayName == sessionUserId.DisplayName) + { + float latency = connection.GetConnectionStats(bReliable: false).m_fLatency; + UserConnections.Add(new UserConnectionInfo(user.DisplayName, latency, user.PlatformID.m_steamId)); + index++; + } + } + } + + + } + + } + catch (Exception) { } + } + try + { + if (UserConnections.Count > 1) + { + //foreach (var info in UserConnections) + //{ + // Log($"{info.DisplayName} - {info.SteamId} - {info.Latency}ms"); + //} + MODEntry.localServer?.BroadcastMessageToAll(UsersMessageType, new UsersMessage(UserConnections)); + UserConnections.Clear(); + } + } + catch (Exception) { } + UserConnections.Clear(); + broadcastTimer = 0.0f; + + } + } @@ -206,11 +273,13 @@ public override void OnDraw(ref Rect rect, GUIStyle style) string nickname = userInfo.Nickname; string nicknamePart = string.IsNullOrEmpty(nickname) ? "" : $" [{nickname}]"; DrawText(ref rect, style, $"{user.DisplayName} (好友 {username}{nicknamePart}) {index}号位 {(latency == 0 ? "获取错误" : (latency * 1000 * 2).ToString("000") + " ms")}"); + } } else { DrawText(ref rect, style, $"{user.DisplayName} {index}号位 {(latency == 0 ? "获取错误" : (latency * 1000 * 2).ToString("000") + " ms")}"); + } index++; } @@ -227,39 +296,48 @@ public override void OnDraw(ref Rect rect, GUIStyle style) MultiplayerController multiplayerController = GameUtils.RequestManager(); Client client = multiplayerController.m_LocalClient; if (client != null) - { - ConnectionStats connectionStats = client.GetConnectionStats(bReliable: false); - FastList clientUserSystem = ClientUserSystem.m_Users; + if (ShowEnabledhost.Value) + { + if (!string.IsNullOrEmpty(serverFriendsMessage)) + DrawText(ref rect, style, serverFriendsMessage); - bool hasChanged = HasUserListChanged(clientUserSystem, clientUserSystem_catched); - if (hasChanged) + } + else { - clientUserSystem_catched = new FastList(); - // 更新存储的用户列表 - clientUserSystem_catched.AddRange(clientUserSystem.ToArray()); - clientFriendsMessage = string.Empty; - // 更新提示信息 - for (int i = 0; i < clientUserSystem.Count; i++) { - User user = clientUserSystem._items[i]; - if (user.IsLocal) - { - continue; - } - CSteamID csteamID = user.PlatformID.m_steamId; - if (EFriendRelationship.k_EFriendRelationshipFriend == SteamFriends.GetFriendRelationship(csteamID)) + ConnectionStats connectionStats = client.GetConnectionStats(bReliable: false); + FastList clientUserSystem = ClientUserSystem.m_Users; + + bool hasChanged = HasUserListChanged(clientUserSystem, clientUserSystem_catched); + if (hasChanged) { - string personaName = SteamFriends.GetFriendPersonaName(csteamID); - string nickname = SteamFriends.GetPlayerNickname(csteamID); - string nicknamePart = string.IsNullOrEmpty(nickname) ? "" : $" [{nickname}]"; - clientFriendsMessage += $"{user.DisplayName} (好友 {personaName}{nickname}) {i + 1}号位\n"; + clientUserSystem_catched = new FastList(); + // 更新存储的用户列表 + clientUserSystem_catched.AddRange(clientUserSystem.ToArray()); + clientFriendsMessage = string.Empty; + // 更新提示信息 + for (int i = 0; i < clientUserSystem.Count; i++) + { + User user = clientUserSystem._items[i]; + if (user.IsLocal) + { + continue; + } + CSteamID csteamID = user.PlatformID.m_steamId; + if (EFriendRelationship.k_EFriendRelationshipFriend == SteamFriends.GetFriendRelationship(csteamID)) + { + string personaName = SteamFriends.GetFriendPersonaName(csteamID); + string nickname = SteamFriends.GetPlayerNickname(csteamID); + string nicknamePart = string.IsNullOrEmpty(nickname) ? "" : $" [{nickname}]"; + clientFriendsMessage += $"{user.DisplayName} (好友 {personaName}{nickname}) {i + 1}号位\n"; + } + } } + + string latencyText = connectionStats.m_fLatency == (float)0 ? "获取错误" : (connectionStats.m_fLatency * 1000 * 2).ToString("000") + " ms"; + DrawText(ref rect, style, $"{clientFriendsMessage}本机 {latencyText}"); } } - - string latencyText = connectionStats.m_fLatency == (float)0 ? "获取错误" : (connectionStats.m_fLatency * 1000 * 2).ToString("000") + " ms"; - DrawText(ref rect, style, $"{clientFriendsMessage}本机 {latencyText}"); - } } catch (Exception) { @@ -285,5 +363,105 @@ private bool HasUserListChanged(FastList currentUserSystem, FastList return false; } } + public static void OnUsersMessage(UsersMessage message) + { + + if (!MODEntry.isHost && ShowEnabledhost.Value) + { + int i = 0; + serverFriendsMessage = string.Empty; + foreach (var info in message.UserConnections) + { + //Log($"{info.DisplayName} - {info.SteamId} - {info.Latency}ms"); + if (info.SteamId == (CSteamID)MODEntry.CurrentSteamID.m_SteamID) + { + serverFriendsMessage += $"本机 {i + 1}号位 {(info.Latency == 0 ? "获取错误" : (info.Latency * 1000 * 2).ToString("000") + " ms")}\n"; + } + else if (EFriendRelationship.k_EFriendRelationshipFriend == SteamFriends.GetFriendRelationship(info.SteamId)) + { + string personaName = SteamFriends.GetFriendPersonaName(info.SteamId); + string nickname = SteamFriends.GetPlayerNickname(info.SteamId); + string nicknamePart = string.IsNullOrEmpty(nickname) ? "" : $" [{nickname}]"; + if (i != 0) + { + serverFriendsMessage += $"{info.DisplayName} (好友 {personaName}{nickname}) {i + 1}号位 {(info.Latency == 0 ? "获取错误" : (info.Latency * 1000 * 2).ToString("000") + " ms")}\n"; + } + serverFriendsMessage += $"{info.DisplayName} (好友 {personaName}{nickname})\n"; + + + } + else + serverFriendsMessage += $"{info.DisplayName} {i + 1}号位 {(info.Latency == 0 ? "获取错误" : (info.Latency * 1000 * 2).ToString("000") + " ms")}\n"; + i++; + } + } + } + [HarmonyPrefix] + [HarmonyPatch(typeof(NetworkMessageTracker), "TrackSentGlobalEvent")] + public static bool NetworkMessageTrackerTrackSentGlobalEventPatch(MessageType type) + { + return type != UsersMessageType; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NetworkMessageTracker), "TrackReceivedGlobalEvent")] + public static bool NetworkMessageTrackerTrackReceivedGlobalEventPatch(MessageType type) + { + return type != UsersMessageType; + } } + + public class UsersMessage : Serialisable + { + public List UserConnections { get; private set; } + + public UsersMessage(List userConnections) + { + UserConnections = userConnections; + } + + public void Serialise(BitStreamWriter writer) + { + writer.Write((uint)UserConnections.Count, 4); // 使用 4 位来写入用户数量 + foreach (var userConnection in UserConnections) + { + writer.Write(userConnection.DisplayName, Encoding.UTF8); + writer.Write(userConnection.Latency); + writer.Write(userConnection.SteamId.m_SteamID, 64); ; // 写入 SteamID + } + } + + public bool Deserialise(BitStreamReader reader) + { + int count = (int)reader.ReadUInt32(4); // 使用 4 位来读取用户数量 + UserConnections = new List(); + + for (int i = 0; i < count; i++) + { + string displayName = reader.ReadString(Encoding.UTF8); + float latency = reader.ReadFloat32(); + ulong steamId = (ulong)reader.ReadUInt64(64); // 读取 SteamID + CSteamID cSteamId = new CSteamID(steamId); + UserConnections.Add(new UserConnectionInfo(displayName, latency, cSteamId)); + } + + return true; + } + } + + + public class UserConnectionInfo + { + public string DisplayName { get; private set; } + public float Latency { get; private set; } + public CSteamID SteamId { get; private set; } + + public UserConnectionInfo(string displayName, float latency, CSteamID steamId) + { + DisplayName = displayName; + Latency = latency; + SteamId = steamId; + } + } + } diff --git a/HostUtilities/HostUtilities/_MODEntry.cs b/HostUtilities/HostUtilities/_MODEntry.cs index 1c708fd..97b879c 100644 --- a/HostUtilities/HostUtilities/_MODEntry.cs +++ b/HostUtilities/HostUtilities/_MODEntry.cs @@ -10,8 +10,12 @@ using UnityEngine; using UnityEngine.Networking; using SimpleJSON; +using Team17.Online.Multiplayer; +using BitStream; +using Team17.Online.Multiplayer.Messaging; using Version = System.Version; + namespace HostUtilities { [BepInPlugin("com.ch3ngyz.plugin.HostUtilities", "[HostUtilities] By.易程不变 Q群860480677 点击下方“‧‧‧”展开", "1.0.87")] @@ -34,7 +38,8 @@ public class MODEntry : BaseUnityPlugin public static bool isSelectedAndPlay = false; public static bool isAuthor = false; public static CSteamID CurrentSteamID; - + private static Harmony patcher; + public static Server localServer = null; public void Awake() { try @@ -67,6 +72,12 @@ public void Awake() UI_DisplayModsOnResultsScreen.Awake(); UI_DisplayKickedUser.Awake(); UI_DisplayLatency.Awake(); + // 创建Harmony实例 + patcher = new Harmony("com.ch3ngyz.plugin.HostUtilities"); + + // 单独调用必要的补丁方法 + ServerMessengerPatch.Patch(patcher); + MailboxPatch.Patch(patcher); HarmonyInstance = Harmony.CreateAndPatchAll(MethodBase.GetCurrentMethod().DeclaringType); AllHarmony[MethodBase.GetCurrentMethod().DeclaringType.Name] = HarmonyInstance; @@ -313,8 +324,63 @@ public static void StartScreenFlow_Awake_Postfix() skipFirstCheck = true; } } + [HarmonyPrefix] + [HarmonyPatch(typeof(Message), "Deserialise")] + public static bool MessageDeserialisePatch(BitStreamReader reader, Message __instance, ref bool __result, ref bool __runOriginal) + { + if (!__runOriginal) return false; + var messageType = (MessageType)reader.ReadByteAhead(8); + if (messageType == UI_DisplayLatency.UsersMessageType) + { + __instance.Type = (MessageType)reader.ReadByte(8); + __instance.Payload = new UsersMessage(new List()); + __instance.Payload.Deserialise(reader); + __result = true; + return false; + } + return true; + } + public static class ServerMessengerPatch + { + public static void Patch(Harmony harmony) + { + harmony.Patch(AccessTools.Method("ServerMessenger:OnServerStarted"), null, new HarmonyMethod(typeof(ServerMessengerPatch).GetMethod("PostfixStarted"))); + harmony.Patch(AccessTools.Method("ServerMessenger:OnServerStopped"), null, new HarmonyMethod(typeof(ServerMessengerPatch).GetMethod("PostfixStopped"))); + } + + public static void PostfixStarted(Server server) + { + MODEntry.localServer = server; + } + public static void PostfixStopped() + { + MODEntry.localServer = null; + } + + + } + public static class MailboxPatch + { + public static void Patch(Harmony harmony) + { + harmony.Patch(AccessTools.Method("Mailbox:OnMessageReceived"), new HarmonyMethod(typeof(MailboxPatch).GetMethod("Prefix")), null); + } + public static bool Prefix(MessageType type, Serialisable message) + { + if (type == UI_DisplayLatency.UsersMessageType) + { + UsersMessage usersMessage = (UsersMessage)message; + UI_DisplayLatency.OnUsersMessage(usersMessage); + return false; + } + return true; + + } + + } } + public class VersionCheckClass : MonoBehaviour { public static void Log(string mes) => MODEntry.LogInfo(MethodBase.GetCurrentMethod().DeclaringType.Name, mes); @@ -529,4 +595,39 @@ private DateTime UnixTimeStampToDateTime(long unixTimeStamp) return dtDateTime; } } + public static class BitStreamReaderExtension + { + private static readonly FieldInfo fieldInfo_bufferLengthInBits = AccessTools.Field(typeof(BitStreamReader), "_bufferLengthInBits"); + private static readonly FieldInfo fieldInfo_cbitsInPartialByte = AccessTools.Field(typeof(BitStreamReader), "_cbitsInPartialByte"); + private static readonly FieldInfo fieldInfo_partialByte = AccessTools.Field(typeof(BitStreamReader), "_partialByte"); + private static readonly FieldInfo fieldInfo_byteArray = AccessTools.Field(typeof(BitStreamReader), "_byteArray"); + private static readonly FieldInfo fieldInfo_byteArrayIndex = AccessTools.Field(typeof(BitStreamReader), "_byteArrayIndex"); + + public static byte ReadByteAhead(this BitStreamReader instance, int countOfBits) + { + if (instance.EndOfStream) return 0; + if (countOfBits > 8 || countOfBits <= 0) return 0; + if ((long)countOfBits > (long)(ulong)(uint)fieldInfo_bufferLengthInBits.GetValue(instance)) return 0; + byte b; + + int cbitsInPartialByte = (int)fieldInfo_cbitsInPartialByte.GetValue(instance); + byte partialByte = (byte)fieldInfo_partialByte.GetValue(instance); + if (cbitsInPartialByte >= countOfBits) + { + int num = 8 - countOfBits; + b = (byte)(partialByte >> num); + } + else + { + byte[] byteArray = (byte[])fieldInfo_byteArray.GetValue(instance); + byte b2 = byteArray[(int)fieldInfo_byteArrayIndex.GetValue(instance)]; + int num2 = 8 - countOfBits; + b = (byte)(partialByte >> num2); + int num3 = num2 + cbitsInPartialByte; + b |= (byte)(b2 >> num3); + } + return b; + } + + } }