using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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.RegularExpressions; using HarmonyLib; using Il2Cpp; using Il2CppDissonance.Integrations.FishNet; using Il2CppEpic.OnlineServices; using Il2CppEpic.OnlineServices.Lobby; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppFishNet.Managing.Server; using Il2CppFishNet.Managing.Timing; using Il2CppFishNet.Object; using Il2CppFishNet.Object.Synchronizing; 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.Lobby; using LobbyKit.Features.PlayerList; using LobbyKit.Features.Settings; using LobbyKit.Patches; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; 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.0.0", "Spyci", null)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Spyci")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3522b61da9e4916bffd54f7fe3492106d7d59219")] [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] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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); } } [CompilerGenerated] private sealed class d__109 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public LobbyKitCore <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__109(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown int num = <>1__state; LobbyKitCore lobbyKitCore = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)lobbyKitCore._playerReferenceManager == (Object)null) { lobbyKitCore._playerReferenceManager = PlayerReferenceManager.Instance; <>2__current = null; <>1__state = 1; return true; } lobbyKitCore.ReferencesLoaded = true; Debug.Log(Object.op_Implicit("[Core] Scene references loaded successfully.")); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } 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; private static MelonPreferences_Category _preferences; 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 PlayerReferenceManager _playerReferenceManager; public bool ReferencesLoaded; 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 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); _serverName = _preferences.CreateEntry("ServerName", string.Empty, "Server Name", "Custom default lobby/server name. Leave empty to use ''s Lobby'.", false, false, (ValueValidator)null, (string)null); _serverCapacity = _preferences.CreateEntry("ServerCapacity", 8, "Server Capacity", "Saved default value for the max players slider.", false, false, (ValueValidator)null, (string)null); _isPublicLobby = _preferences.CreateEntry("IsPublicLobby", true, "Public Lobby", "Saved default for public/private lobby.", false, false, (ValueValidator)null, (string)null); _isPasswordProtected = _preferences.CreateEntry("IsPasswordProtected", false, "Password Protected", "Saved default for password protection.", false, false, (ValueValidator)null, (string)null); _lobbyPassword = _preferences.CreateEntry("LobbyPassword", string.Empty, "Lobby Password", "Saved default lobby password.", false, false, (ValueValidator)null, (string)null); _isPeacefulMode = _preferences.CreateEntry("IsPeacefulMode", false, "Peaceful Mode", "Saved default for peaceful mode.", false, false, (ValueValidator)null, (string)null); _isTextChatOnly = _preferences.CreateEntry("IsTextChatOnly", false, "Text Chat Only", "Saved default for text-chat-only mode.", false, false, (ValueValidator)null, (string)null); _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); MelonPreferences.Save(); ((MelonBase)this).HarmonyInstance.PatchAll(); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { ReferencesLoaded = false; players.Clear(); _playerJoinTimes.Clear(); _playerJoinTimesByProductId.Clear(); MelonCoroutines.Start(LoadReferences()); } public void PlayerJoinedGame(PlayerReference p) { if (p != null) { bool flag = p.IsLocalPlayerInstance(); bool flag2 = !players.Any((PlayerReference player) => player != null && player.ConnectionID == p.ConnectionID); if (flag2) { players.Add(p); } if (flag2 && (isHost || flag)) { TrackPlayerJoinTime(p, GetLobbyUptimeSeconds()); } if (flag) { localPlayer = p; isHost = p.ConnectionID == 32767; } else if (isHost && flag2 && ShowJoinMessages) { string value = ChatSystem.AutoCloseTmpRichText(string.IsNullOrWhiteSpace(p.Username) ? "A player" : p.Username); ChatSystem.BroadcastSystemMessage($"<#FA0>{value} joined."); } if (isHost) { ChatSystem.SendMotdToPlayer(p); } } } public void PlayerLeftGame(PlayerReference removedPlayer) { if (removedPlayer != null) { bool flag = players.Any((PlayerReference player) => player != null && player.ConnectionID == removedPlayer.ConnectionID); bool flag2 = removedPlayer.IsLocalPlayerInstance(); if (isHost && flag && !flag2 && ShowLeaveMessages) { string value = ChatSystem.AutoCloseTmpRichText(string.IsNullOrWhiteSpace(removedPlayer.Username) ? "A player" : removedPlayer.Username); ChatSystem.BroadcastSystemMessage($"<#FA0>{value} left."); } 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); if (localPlayer != null && localPlayer.ConnectionID == removedPlayer.ConnectionID) { localPlayer = null; isHost = false; } } } 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); } 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; } 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 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(); } } [IteratorStateMachine(typeof(d__109))] private IEnumerator LoadReferences() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__109(0) { <>4__this = this }; } } 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