using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using HarmonyLib; using Il2Cpp; using Il2CppDissonance.Integrations.FishNet; using Il2CppEpic.OnlineServices; using Il2CppEpic.OnlineServices.Lobby; using Il2CppFishNet; using Il2CppFishNet.Component.Transforming; using Il2CppFishNet.Connection; using Il2CppFishNet.Managing; using Il2CppFishNet.Managing.Client; using Il2CppFishNet.Managing.Logging; using Il2CppFishNet.Managing.Server; using Il2CppFishNet.Managing.Timing; using Il2CppFishNet.Object; using Il2CppFishNet.Object.Synchronizing; using Il2CppFishNet.Serializing; using Il2CppFishNet.Transporting; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppPlayEveryWare.EpicOnlineServices.Samples; using Il2CppSystem; using Il2CppSystem.Reflection; using Il2CppTMPro; using Il2CppTankAndHealerStudioAssets; using Il2Cpp_Scripts.Managers; using Il2Cpp_Scripts.Systems.Chat; using Il2Cpp_Scripts.UI.Components; using Il2Cpp_Scripts.UI.Pre_Game; using LobbyKit; using LobbyKit.Features.Anticheat; using LobbyKit.Features.Fixes; using LobbyKit.Features.Lobby; using LobbyKit.Features.Permissions; using LobbyKit.Features.PlayerList; using LobbyKit.Features.Settings; using LobbyKit.Patches; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(LobbyKitCore), "LobbyKit", "1.1.3", "Spyci", null)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Spyci")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.3")] [assembly: AssemblyInformationalVersion("1.1.3+f32ff69e55d405912b4489cc1e65b498321c45ef")] [assembly: AssemblyProduct("LobbyKit")] [assembly: AssemblyTitle("LobbyKit")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LobbyKit { public class LobbyKitCore : MelonMod { [HarmonyPatch(typeof(PlayerReferenceManager), "OnPlayerReferenceAdded")] public static class PlayerJoinPatch { private static void Postfix(PlayerReferenceManager __instance, int index) { SyncList playerReferences = __instance.GetPlayerReferences(); if (playerReferences == null || index < 0 || index >= playerReferences.Count) { return; } PlayerReference val = playerReferences[index]; if (val != null) { if (val.IsLocalPlayerInstance()) { Instance.localPlayer = val; isHost = val.ConnectionID == 32767; } Instance.PlayerJoinedGame(val); } } } [HarmonyPatch(typeof(PlayerReferenceManager), "OnPlayerReferenceRemoved")] public static class PlayerLeavePatch { private static void Postfix(PlayerReferenceManager __instance, int index, PlayerReference removedItem) { Instance.PlayerLeftGame(removedItem); } } [HarmonyPatch(typeof(PlayerReferenceManager), "Server_AddPlayerReference")] public static class DedicatedServerJoinPatch { private static void Postfix(PlayerReferenceManager __instance, int connectionId) { if (Instance != null && Application.isBatchMode && connectionId != 32767) { isHost = true; PlayerReference val = FindByConnectionId(__instance, connectionId); if (val != null) { Instance.PlayerJoinedGame(val); } } } } [HarmonyPatch(typeof(PlayerReferenceManager), "Server_RemovePlayerReference")] public static class DedicatedServerLeavePatch { private static void Prefix(PlayerReferenceManager __instance, int playerNetId) { if (Instance != null && Application.isBatchMode && playerNetId != 32767) { isHost = true; PlayerReference val = FindByConnectionId(__instance, playerNetId); if (val != null) { Instance.PlayerLeftGame(val); } } } } public static LobbyKitCore Instance; private PlayerReference localPlayer; private readonly List players = new List(); private readonly Dictionary _playerJoinTimes = new Dictionary(); private readonly Dictionary _playerJoinTimesByProductId = new Dictionary(); public static bool isHost; public static bool WasHosting; private static MelonPreferences_Category _preferences; private static MelonPreferences_Category _serverSettings; private static MelonPreferences_Entry _enableGuestBangCommands; private static MelonPreferences_Entry _serverName; private static MelonPreferences_Entry _serverCapacity; private static MelonPreferences_Entry _searchMinPlayers; private static MelonPreferences_Entry _searchMaxPlayers; private static MelonPreferences_Entry _searchShowLocked; private static MelonPreferences_Entry _searchShowModded; private static MelonPreferences_Entry _searchShowYourLanguageOnly; private static MelonPreferences_Entry _searchOnlyPeacefulLobbies; private static MelonPreferences_Entry _searchHidePeacefulLobbies; private static MelonPreferences_Entry _searchRegionMode; private static MelonPreferences_Entry _isPublicLobby; private static MelonPreferences_Entry _isPasswordProtected; private static MelonPreferences_Entry _lobbyPassword; private static MelonPreferences_Entry _isPeacefulMode; private static MelonPreferences_Entry _isTextChatOnly; private static MelonPreferences_Entry _messageOfTheDay; private static MelonPreferences_Entry _showJoinMessages; private static MelonPreferences_Entry _showLeaveMessages; private static MelonPreferences_Entry _joinMessageSize; private static MelonPreferences_Entry _leaveMessageSize; private static MelonPreferences_Entry _autoRestartOnCrash; private static MelonPreferences_Entry _showKickBanMessages; private static MelonPreferences_Entry _blockThrowingSpam; private static MelonPreferences_Entry _blockPlayerSizeCheat; private static MelonPreferences_Entry _blockFlyingSleds; private static MelonPreferences_Entry> _chatPrefixes; private PlayerReferenceManager _playerReferenceManager; public bool ReferencesLoaded; private string _lastPlayerCountLog; public static bool EnableGuestBangCommands => _enableGuestBangCommands?.Value ?? true; public static string ServerName => _serverName?.Value ?? string.Empty; public static int ServerCapacity => _serverCapacity?.Value ?? 8; public static int SearchMinPlayers => _searchMinPlayers?.Value ?? 1; public static int SearchMaxPlayers => _searchMaxPlayers?.Value ?? 0; public static bool SearchShowLocked => _searchShowLocked?.Value ?? true; public static bool SearchShowModded => _searchShowModded?.Value ?? true; public static bool SearchShowYourLanguageOnly => _searchShowYourLanguageOnly?.Value ?? false; public static bool SearchOnlyPeacefulLobbies => _searchOnlyPeacefulLobbies?.Value ?? false; public static bool SearchHidePeacefulLobbies => _searchHidePeacefulLobbies?.Value ?? false; public static int SearchRegionMode => _searchRegionMode?.Value ?? 1; public static bool IsPublicLobby => _isPublicLobby?.Value ?? true; public static bool IsPasswordProtected => _isPasswordProtected?.Value ?? false; public static string LobbyPassword => _lobbyPassword?.Value ?? string.Empty; public static bool IsPeacefulMode => _isPeacefulMode?.Value ?? false; public static bool IsTextChatOnly => _isTextChatOnly?.Value ?? false; public static string MessageOfTheDay => _messageOfTheDay?.Value ?? string.Empty; public static bool ShowJoinMessages => _showJoinMessages?.Value ?? true; public static bool ShowLeaveMessages => _showLeaveMessages?.Value ?? true; public static int JoinMessageSize => _joinMessageSize?.Value ?? 75; public static int LeaveMessageSize => _leaveMessageSize?.Value ?? 75; public static bool AutoRestartOnCrash => _autoRestartOnCrash?.Value ?? false; public static bool ShowKickBanMessages => _showKickBanMessages?.Value ?? true; public static bool BlockThrowingSpam => _blockThrowingSpam?.Value ?? true; public static bool BlockPlayerSizeCheat => _blockPlayerSizeCheat?.Value ?? true; public static bool BlockFlyingSleds => _blockFlyingSleds?.Value ?? true; private int RemotePlayerCount => players.Count((PlayerReference pl) => pl != null && pl.ConnectionID != 32767); public static string ChatPrefixFor(PermLevel level) { int num = (int)(level - 1); List list = _chatPrefixes?.Value; if (list == null || num < 0 || num >= list.Count || list[num] == null) { return string.Empty; } return list[num]; } public void SavePrefs() { MelonPreferences.Save(); } public override void OnInitializeMelon() { Instance = this; _preferences = MelonPreferences.CreateCategory("LobbyKit", "LobbyKit"); _enableGuestBangCommands = _preferences.CreateEntry("EnableGuestBangCommands", true, "Enable Guest Bang Commands", "Allow non-host players to use custom bang chat commands like !tp.", false, false, (ValueValidator)null, (string)null); _serverSettings = MelonPreferences.CreateCategory("ServerSettings", "Server Settings"); _serverName = GetOrCreate(_serverSettings, "ServerName", string.Empty, "Server Name", "Custom default lobby/server name. Leave empty to use ''s Lobby'."); _serverCapacity = GetOrCreate(_serverSettings, "ServerCapacity", 8, "Server Capacity", "Saved default value for the max players slider."); _isPublicLobby = GetOrCreate(_serverSettings, "IsPublicLobby", defaultValue: true, "Public Lobby", "Saved default for public/private lobby."); _isPasswordProtected = GetOrCreate(_serverSettings, "IsPasswordProtected", defaultValue: false, "Password Protected", "Saved default for password protection."); _lobbyPassword = GetOrCreate(_serverSettings, "LobbyPassword", string.Empty, "Lobby Password", "Saved default lobby password."); _isPeacefulMode = GetOrCreate(_serverSettings, "IsPeacefulMode", defaultValue: false, "Peaceful Mode", "Saved default for peaceful mode."); _isTextChatOnly = GetOrCreate(_serverSettings, "IsTextChatOnly", defaultValue: false, "Text Chat Only", "Saved default for text-chat-only mode."); MigrateServerSetting(_preferences, "ServerName", _serverName, string.Empty); MigrateServerSetting(_preferences, "ServerCapacity", _serverCapacity, 8); MigrateServerSetting(_preferences, "IsPublicLobby", _isPublicLobby, defaultValue: true); MigrateServerSetting(_preferences, "IsPasswordProtected", _isPasswordProtected, defaultValue: false); MigrateServerSetting(_preferences, "LobbyPassword", _lobbyPassword, string.Empty); MigrateServerSetting(_preferences, "IsPeacefulMode", _isPeacefulMode, defaultValue: false); MigrateServerSetting(_preferences, "IsTextChatOnly", _isTextChatOnly, defaultValue: false); _searchMaxPlayers = _preferences.CreateEntry("SearchMaxPlayers", 0, "Search Max Players", "Maximum players filter for lobby search. 0 == Any.", false, false, (ValueValidator)null, (string)null); _searchShowLocked = _preferences.CreateEntry("SearchShowLocked", true, "Search Show Locked", "Include locked lobbies in search results.", false, false, (ValueValidator)null, (string)null); _searchShowModded = _preferences.CreateEntry("SearchShowModded", true, "Search Show Modded", "Include modded lobbies in search results.", false, false, (ValueValidator)null, (string)null); _searchShowYourLanguageOnly = _preferences.CreateEntry("SearchShowYourLanguageOnly", false, "Search Your Language Only", "Filter to language-matching lobbies.", false, false, (ValueValidator)null, (string)null); _searchOnlyPeacefulLobbies = _preferences.CreateEntry("SearchOnlyPeacefulLobbies", false, "Search Only Peaceful Lobbies", "Only show peaceful-mode lobbies in search results.", false, false, (ValueValidator)null, (string)null); _searchHidePeacefulLobbies = _preferences.CreateEntry("SearchHidePeacefulLobbies", false, "Search Hide Peaceful Lobbies", "Hide peaceful-mode lobbies in search results.", false, false, (ValueValidator)null, (string)null); _searchRegionMode = _preferences.CreateEntry("SearchRegionMode", 1, "Search Region Mode", "Lobby search region filter: 0 near, 1 nearby regions, 2 far, 3 worldwide.", false, false, (ValueValidator)null, (string)null); _messageOfTheDay = _preferences.CreateEntry("MessageOfTheDay", string.Empty, "Message of the Day", "Private chat message sent to each player when they join your hosted lobby. Leave empty to disable.", false, false, (ValueValidator)null, (string)null); _showJoinMessages = _preferences.CreateEntry("ShowJoinMessages", true, "Show Join Messages", "Broadcast a chat message when a player joins your hosted lobby.", false, false, (ValueValidator)null, (string)null); _showLeaveMessages = _preferences.CreateEntry("ShowLeaveMessages", true, "Show Leave Messages", "Broadcast a chat message when a player leaves your hosted lobby.", false, false, (ValueValidator)null, (string)null); _joinMessageSize = _preferences.CreateEntry("JoinMessageSize", 75, "Join Message Size", "Font size percentage for join messages (e.g. 75 for 75%).", false, false, (ValueValidator)null, (string)null); _leaveMessageSize = _preferences.CreateEntry("LeaveMessageSize", 75, "Leave Message Size", "Font size percentage for leave messages (e.g. 75 for 75%).", false, false, (ValueValidator)null, (string)null); _autoRestartOnCrash = _preferences.CreateEntry("AutoRestartOnCrash", false, "Auto-Restart On Crash", "Automatically re-host the lobby when it crashes unexpectedly.", false, false, (ValueValidator)null, (string)null); _showKickBanMessages = _preferences.CreateEntry("ShowKickBanMessages", true, "Show Kick/Ban Messages", "Broadcast a chat message when a player is kicked or banned from your hosted lobby.", false, false, (ValueValidator)null, (string)null); _blockThrowingSpam = _preferences.CreateEntry("BlockThrowingSpam", true, "Block Throwing Spam", "Anticheat: rate-limit and kick clients who spam server RPCs (e.g. rapid throwing).", false, false, (ValueValidator)null, (string)null); _blockPlayerSizeCheat = _preferences.CreateEntry("BlockPlayerSizeCheat", true, "Block Player Size Cheat", "Anticheat: force each player's avatar to their allowed size (default 1, or their !size choice) and clamp any cheated scale back.", false, false, (ValueValidator)null, (string)null); _blockFlyingSleds = _preferences.CreateEntry("BlockFlyingSleds", true, "Block Flying Sleds", "Anticheat: no-op the client-initiated Cmd_PushSled (a raw AddForce lever used to fly sleds). Boosts use a separate path and are unaffected.", false, false, (ValueValidator)null, (string)null); _chatPrefixes = _preferences.CreateEntry>("ChatPrefixes", new List { "<#7DFF7D>[M] ", "<#7DD0FF>[A] ", "<#FFE066>[O] " }, "Chat Prefixes", "Prefixes shown before a name in chat, by level: [Mod, Admin, Owner]. Empty string disables that level's prefix.", false, false, (ValueValidator)null, (string)null); Perms.Initialize(); ChatSystem.SeedCommandLevels(); MelonPreferences.Save(); ((MelonBase)this).HarmonyInstance.PatchAll(); GenericServerRpcRateLimitPatch.ApplyPatches(((MelonBase)this).HarmonyInstance); PlayerScalePacketClamp.Apply(((MelonBase)this).HarmonyInstance); SledPushBlockPatch.Apply(((MelonBase)this).HarmonyInstance); CannonLaunchFix.Apply(((MelonBase)this).HarmonyInstance); MelonCoroutines.Start(PlayerCountLogLoop()); } private static MelonPreferences_Entry GetOrCreate(MelonPreferences_Category category, string identifier, T defaultValue, string displayName, string description = null) { if (!category.HasEntry(identifier)) { return category.CreateEntry(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null); } return category.GetEntry(identifier); } private static void MigrateServerSetting(MelonPreferences_Category oldCategory, string key, MelonPreferences_Entry target, T defaultValue) { try { if (oldCategory != null && target != null) { MelonPreferences_Entry val = oldCategory.CreateEntry(key, defaultValue, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); bool flag = EqualityComparer.Default.Equals(val.Value, defaultValue); if (EqualityComparer.Default.Equals(target.Value, defaultValue) && !flag) { target.Value = val.Value; } oldCategory.DeleteEntry(key); } } catch (Exception ex) { MelonLogger.Warning($"[LobbyKit] ServerSettings migrate '{key}' failed: {ex.GetType().Name}: {ex.Message}"); } } public override void OnLateUpdate() { if (BlockPlayerSizeCheat) { PlayerScaleEnforcer.Tick(); } } private IEnumerator PlayerCountLogLoop() { while (true) { yield return (object)new WaitForSecondsRealtime(60f); if (isHost) { int remotePlayerCount = RemotePlayerCount; string value = ((remotePlayerCount > 0) ? (" [" + string.Join(", ", from pl in players where pl != null && pl.ConnectionID != 32767 select ChatSystem.StripRichText(string.IsNullOrWhiteSpace(pl.Username) ? "?" : pl.Username)) + "]") : ""); string text = $"[LobbyKit] Players online: {remotePlayerCount}{value}"; if (!(text == _lastPlayerCountLog)) { _lastPlayerCountLog = text; MelonLogger.Msg(text); } } } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { ReferencesLoaded = false; ResetLobbyTrackingState("scene loaded"); ChatSystem.ResetSessionState(); MelonCoroutines.Start(LoadReferences()); } private static PlayerReference FindByConnectionId(PlayerReferenceManager prm, int connectionId) { SyncList playerReferences = prm.GetPlayerReferences(); if (playerReferences == null) { return null; } for (int i = 0; i < playerReferences.Count; i++) { PlayerReference val = playerReferences[i]; if (val != null && val.ConnectionID == connectionId) { return val; } } return null; } public void PlayerJoinedGame(PlayerReference p) { if (p == null) { return; } if (isHost && p.ConnectionID != 32767 && !p.IsLocalPlayerInstance() && Perms.IsBanned(p.ProductUserId)) { KickAnnouncer.Enqueue(p.ConnectionID, "is banned"); try { ServerManager serverManager = InstanceFinder.ServerManager; if (serverManager != null) { serverManager.Kick(p.ConnectionID, (KickReason)0, (LoggingType)3, ""); } } catch { } MelonLogger.Msg($"[LobbyKit] Banned player kicked on join (conn {p.ConnectionID}, puid {p.ProductUserId})."); return; } if (isHost && p.ConnectionID != 32767) { Perms.RememberName(p.ProductUserId, ChatSystem.StripRichText(p.Username)); } double lobbyUptimeSeconds = GetLobbyUptimeSeconds(); bool flag = p.IsLocalPlayerInstance(); bool flag2 = !players.Any((PlayerReference player) => player != null && player.ConnectionID == p.ConnectionID); if (flag2) { players.Add(p); } if (isHost || flag) { if (!string.IsNullOrWhiteSpace(p.ProductUserId) && _playerJoinTimesByProductId.TryGetValue(p.ProductUserId, out var value) && value > lobbyUptimeSeconds + 1.0) { _playerJoinTimesByProductId[p.ProductUserId] = lobbyUptimeSeconds; } if (flag2) { TrackPlayerJoinTime(p, lobbyUptimeSeconds); } } if (flag) { localPlayer = p; isHost = p.ConnectionID == 32767; if (isHost) { WasHosting = true; SilentCrashDetectionPatches.StartPolling(); } } else if (isHost && flag2) { if (ShowJoinMessages) { string value2 = ChatSystem.AutoCloseTmpRichText(string.IsNullOrWhiteSpace(p.Username) ? "A player" : p.Username); ChatSystem.BroadcastSystemMessage($"<#FA0>{value2} joined."); } string value3 = ChatSystem.StripRichText(string.IsNullOrWhiteSpace(p.Username) ? "A player" : p.Username); MelonLogger.Msg($"[LobbyKit] + {value3} joined (conn {p.ConnectionID}, puid {p.ProductUserId}) — {RemotePlayerCount} online."); if (PlayerSizeRegistry.AnySizes) { MelonCoroutines.Start(RepushSizesForJoin()); } } if (isHost) { ChatSystem.SendMotdToPlayer(p); } try { string connectionAddress = InstanceFinder.TransportManager.Transport.GetConnectionAddress(p.ConnectionID); Debug.Log(Object.op_Implicit($"Client {p.ConnectionID} connection ID: {connectionAddress}")); } catch (Exception ex) { Debug.LogError(Object.op_Implicit($"Error occurred while fetching connection ID for client {p.ConnectionID}: {ex.Message}")); } } public void PlayerLeftGame(PlayerReference removedPlayer) { if (removedPlayer == null) { return; } bool flag = players.Any((PlayerReference player) => player != null && player.ConnectionID == removedPlayer.ConnectionID); bool flag2 = removedPlayer.IsLocalPlayerInstance(); if (isHost && flag && !flag2) { string value = ChatSystem.AutoCloseTmpRichText(string.IsNullOrWhiteSpace(removedPlayer.Username) ? "A player" : removedPlayer.Username); string value2 = ChatSystem.StripRichText(string.IsNullOrWhiteSpace(removedPlayer.Username) ? "A player" : removedPlayer.Username); int value3 = RemotePlayerCount - 1; if (KickAnnouncer.TryConsume(removedPlayer.ConnectionID, out var reason)) { if (ShowKickBanMessages) { ChatSystem.BroadcastSystemMessage($"<#F44>{value} {reason}."); } MelonLogger.Msg($"[LobbyKit] - {value2} {reason} (conn {removedPlayer.ConnectionID}) — {value3} online."); } else { if (ShowLeaveMessages) { ChatSystem.BroadcastSystemMessage($"<#FA0>{value} left."); } MelonLogger.Msg($"[LobbyKit] - {value2} left (conn {removedPlayer.ConnectionID}) — {value3} online."); } } players.RemoveAll((PlayerReference player) => player == null || player.ConnectionID == removedPlayer.ConnectionID); _playerJoinTimes.Remove(removedPlayer.ConnectionID); if (!string.IsNullOrWhiteSpace(removedPlayer.ProductUserId)) { _playerJoinTimesByProductId.Remove(removedPlayer.ProductUserId); } ChatSystem.ForgetMotdRecipient(removedPlayer.ConnectionID); ChatSystem.ForgetTeleportRequests(removedPlayer.ConnectionID); PlayerSizeRegistry.Remove(removedPlayer.ConnectionID); if (localPlayer != null && localPlayer.ConnectionID == removedPlayer.ConnectionID) { localPlayer = null; isHost = false; SilentCrashDetectionPatches.StopPolling(); ResetLobbyTrackingState("local player removed"); } } public PlayerReference GetLocalPlayer() { if (localPlayer == null) { return null; } return localPlayer; } public bool TryGetPlayerJoinTime(PlayerReference playerReference, out double joinTimeSeconds) { joinTimeSeconds = 0.0; if (!isHost || playerReference == null) { return false; } return _playerJoinTimes.TryGetValue(playerReference.ConnectionID, out joinTimeSeconds); } public bool TryGetPlayerSessionDuration(PlayerReference playerReference, out double sessionSeconds) { sessionSeconds = 0.0; if (!isHost || playerReference == null) { return false; } double lobbyUptimeSeconds = GetLobbyUptimeSeconds(); if (!_playerJoinTimes.TryGetValue(playerReference.ConnectionID, out var value)) { value = ((playerReference.ConnectionID == 32767) ? 0.0 : lobbyUptimeSeconds); TrackPlayerJoinTime(playerReference, value); } else if (value > lobbyUptimeSeconds + 1.0) { value = ((playerReference.ConnectionID == 32767) ? 0.0 : lobbyUptimeSeconds); TrackPlayerJoinTime(playerReference, value); } sessionSeconds = lobbyUptimeSeconds - value; return true; } public bool TryGetPlayerSessionDuration(string productUserId, out double sessionSeconds) { sessionSeconds = 0.0; if (!isHost || string.IsNullOrWhiteSpace(productUserId)) { return false; } double lobbyUptimeSeconds = GetLobbyUptimeSeconds(); if (!_playerJoinTimesByProductId.TryGetValue(productUserId, out var value)) { value = lobbyUptimeSeconds; _playerJoinTimesByProductId[productUserId] = value; } else if (value > lobbyUptimeSeconds + 1.0) { value = lobbyUptimeSeconds; _playerJoinTimesByProductId[productUserId] = value; } sessionSeconds = lobbyUptimeSeconds - value; return true; } public bool TryGetPlayerListTimerDuration(string productUserId, bool isLobbyOwner, out double sessionSeconds) { sessionSeconds = 0.0; if (string.IsNullOrWhiteSpace(productUserId)) { return false; } if (!isHost) { double lobbyUptimeSeconds = GetLobbyUptimeSeconds(); if (isLobbyOwner) { sessionSeconds = lobbyUptimeSeconds; return true; } if (!IsLocalPlayerProductId(productUserId)) { return false; } if (!_playerJoinTimesByProductId.TryGetValue(productUserId, out var value)) { value = lobbyUptimeSeconds; _playerJoinTimesByProductId[productUserId] = value; } sessionSeconds = lobbyUptimeSeconds - value; return true; } return TryGetPlayerSessionDuration(productUserId, out sessionSeconds); } private bool IsLocalPlayerProductId(string productUserId) { if (localPlayer != null && !string.IsNullOrWhiteSpace(localPlayer.ProductUserId)) { return string.Equals(localPlayer.ProductUserId, productUserId, StringComparison.Ordinal); } return false; } private void TrackPlayerJoinTime(PlayerReference playerReference, double joinTimeSeconds) { _playerJoinTimes[playerReference.ConnectionID] = joinTimeSeconds; if (!string.IsNullOrWhiteSpace(playerReference.ProductUserId)) { _playerJoinTimesByProductId[playerReference.ProductUserId] = joinTimeSeconds; } } public void ResetLobbyTrackingState(string reason = null) { players.Clear(); _playerJoinTimes.Clear(); _playerJoinTimesByProductId.Clear(); if (!string.IsNullOrWhiteSpace(reason)) { Debug.Log(Object.op_Implicit("[LobbyKit] Reset lobby tracking state (" + reason + ").")); } } public static string FormatLobbyJoinTime(double uptimeSeconds) { if (uptimeSeconds < 0.0) { uptimeSeconds = 0.0; } TimeSpan timeSpan = TimeSpan.FromSeconds(uptimeSeconds); if (!(timeSpan.TotalHours >= 1.0)) { return $"{(int)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}"; } return $"{(int)timeSpan.TotalHours}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}"; } private static double GetLobbyUptimeSeconds() { TimeManager timeManager = InstanceFinder.TimeManager; if ((Object)(object)timeManager == (Object)null) { return Time.unscaledTimeAsDouble; } if (timeManager.Tick != 0) { return timeManager.TicksToTime(timeManager.Tick); } if (timeManager.ServerUptime > 0f) { return timeManager.ServerUptime; } if (timeManager.ClientUptime > 0f) { return timeManager.ClientUptime; } return Time.unscaledTimeAsDouble; } public static void SetEnableGuestBangCommands(bool value) { if (_enableGuestBangCommands != null) { _enableGuestBangCommands.Value = value; MelonPreferences.Save(); } } public static void SetServerName(string value) { if (_serverName != null) { _serverName.Value = value ?? string.Empty; MelonPreferences.Save(); } } public static void SetServerCapacity(int value) { if (_serverCapacity != null) { _serverCapacity.Value = Math.Clamp(value, 1, 64); MelonPreferences.Save(); } } public static void SetSearchMinPlayers(int value) { if (_searchMinPlayers != null) { _searchMinPlayers.Value = Math.Clamp(value, 1, 64); MelonPreferences.Save(); } } public static void SetSearchMaxPlayers(int value) { if (_searchMaxPlayers != null) { _searchMaxPlayers.Value = ((value != 0) ? Math.Clamp(value, 1, 64) : 0); MelonPreferences.Save(); } } public static void SetSearchShowLocked(bool value) { if (_searchShowLocked != null) { _searchShowLocked.Value = value; MelonPreferences.Save(); } } public static void SetSearchShowModded(bool value) { if (_searchShowModded != null) { _searchShowModded.Value = value; MelonPreferences.Save(); } } public static void SetSearchShowYourLanguageOnly(bool value) { if (_searchShowYourLanguageOnly != null) { _searchShowYourLanguageOnly.Value = value; MelonPreferences.Save(); } } public static void SetSearchOnlyPeacefulLobbies(bool value) { if (_searchOnlyPeacefulLobbies != null) { _searchOnlyPeacefulLobbies.Value = value; if (value && _searchHidePeacefulLobbies != null) { _searchHidePeacefulLobbies.Value = false; } MelonPreferences.Save(); } } public static void SetSearchHidePeacefulLobbies(bool value) { if (_searchHidePeacefulLobbies != null) { _searchHidePeacefulLobbies.Value = value; if (value && _searchOnlyPeacefulLobbies != null) { _searchOnlyPeacefulLobbies.Value = false; } MelonPreferences.Save(); } } public static void SetSearchRegionMode(int value) { if (_searchRegionMode != null) { _searchRegionMode.Value = Math.Clamp(value, 0, 3); MelonPreferences.Save(); } } public static void SetIsPublicLobby(bool value) { if (_isPublicLobby != null) { _isPublicLobby.Value = value; MelonPreferences.Save(); } } public static void SetIsPasswordProtected(bool value) { if (_isPasswordProtected != null) { _isPasswordProtected.Value = value; MelonPreferences.Save(); } } public static void SetLobbyPassword(string value) { if (_lobbyPassword != null) { _lobbyPassword.Value = value ?? string.Empty; MelonPreferences.Save(); } } public static void SetIsPeacefulMode(bool value) { if (_isPeacefulMode != null) { _isPeacefulMode.Value = value; MelonPreferences.Save(); } } public static void SetIsTextChatOnly(bool value) { if (_isTextChatOnly != null) { _isTextChatOnly.Value = value; MelonPreferences.Save(); } } public static void SetMessageOfTheDay(string value) { if (_messageOfTheDay != null) { _messageOfTheDay.Value = value ?? string.Empty; MelonPreferences.Save(); } } public static void SetShowJoinMessages(bool value) { if (_showJoinMessages != null) { _showJoinMessages.Value = value; MelonPreferences.Save(); } } public static void SetShowLeaveMessages(bool value) { if (_showLeaveMessages != null) { _showLeaveMessages.Value = value; MelonPreferences.Save(); } } public static void SetJoinMessageSize(int value) { if (_joinMessageSize != null) { _joinMessageSize.Value = Math.Clamp(value, 50, 100); MelonPreferences.Save(); } } public static void SetLeaveMessageSize(int value) { if (_leaveMessageSize != null) { _leaveMessageSize.Value = Math.Clamp(value, 50, 100); MelonPreferences.Save(); } } public static void SetBlockThrowingSpam(bool value) { if (_blockThrowingSpam != null) { _blockThrowingSpam.Value = value; MelonPreferences.Save(); } } public static void SetBlockPlayerSizeCheat(bool value) { if (_blockPlayerSizeCheat != null) { _blockPlayerSizeCheat.Value = value; MelonPreferences.Save(); } } public static void SetBlockFlyingSleds(bool value) { if (_blockFlyingSleds != null) { _blockFlyingSleds.Value = value; MelonPreferences.Save(); } } public static void SetChatPrefix(PermLevel level, string value) { if (_chatPrefixes == null) { return; } int num = (int)(level - 1); if (num >= 0) { List list = _chatPrefixes.Value ?? new List(); while (list.Count <= num) { list.Add(string.Empty); } list[num] = value ?? string.Empty; _chatPrefixes.Value = list; MelonPreferences.Save(); } } public static void SetAutoRestartOnCrash(bool value) { if (_autoRestartOnCrash != null) { _autoRestartOnCrash.Value = value; MelonPreferences.Save(); } } public static void SetShowKickBanMessages(bool value) { if (_showKickBanMessages != null) { _showKickBanMessages.Value = value; MelonPreferences.Save(); } } private static IEnumerator RepushSizesForJoin() { float[] array = new float[6] { 0f, 0.1f, 2f, 2f, 3f, 5f }; float[] array2 = array; foreach (float num in array2) { yield return (object)new WaitForSecondsRealtime(num); PlayerScalePacketClamp.RepushAllSizes(); } } private IEnumerator LoadReferences() { while ((Object)_playerReferenceManager == (Object)null) { _playerReferenceManager = PlayerReferenceManager.Instance; yield return null; } ReferencesLoaded = true; Debug.Log(Object.op_Implicit("[Core] Scene references loaded successfully.")); } } internal static class NativeUiBackend { private static int _lastAutoCaptureFrame = -1; private static readonly Color FallbackPanelColor = new Color(0.09f, 0.5f, 0.74f, 0.97f); private static readonly Color FallbackInputColor = new Color(0.95f, 0.96f, 0.98f, 1f); private static readonly Color FallbackToggleCheckColor = new Color(0.17f, 0.74f, 0.45f, 1f); internal static NativeUiTemplateSet Defaults { get; } = new NativeUiTemplateSet(); private static void SetDefaults(NativeUiTemplateSet references, bool overwriteExisting = true) { if (references != null) { Defaults.Button = PickDefault