using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using Steamworks.Data; using TMPro; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LobbyFilter")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LobbyFilter")] [assembly: AssemblyTitle("LobbyFilter")] [assembly: AssemblyVersion("1.0.0.0")] [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 LobbyFilter { [BepInPlugin("lobbyfilter", "Lobby Filter", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { [CompilerGenerated] private static class <>O { public static Action <0>__OnBeginCameraRendering; public static CameraCallback <1>__OnCameraPreCull; public static Action <2>__OnApplicationQuitting; } public const string PluginGuid = "lobbyfilter"; public const string PluginName = "Lobby Filter"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource ModLog; internal static ConfigEntry PollInterval; internal static ConfigEntry InvalidRemoveDelay; internal static ConfigEntry Logging; private static Harmony harmony; private static bool callbacksRegistered; private static int lastProcessedFrame = -1; internal static bool LoggingEnabled { get { if (Logging != null) { return Logging.Value; } return false; } } private void Awake() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown ModLog = ((BaseUnityPlugin)this).Logger; BindConfig(); LogInfo("Lobby Filter 1.0.0 loaded."); harmony = new Harmony("lobbyfilter"); harmony.PatchAll(); RegisterStaticCallbacks(); LogInfo("Stable lobby cache enabled; automatic refreshes no longer rebuild the whole list."); } private void BindConfig() { PollInterval = ((BaseUnityPlugin)this).Config.Bind("General", "PollInterval", 0.125f, "Seconds between updates. Lower values update faster."); InvalidRemoveDelay = ((BaseUnityPlugin)this).Config.Bind("General", "InvalidRemoveDelay", 30f, "Seconds a lobby must remain continuously unjoinable before its removed."); Logging = ((BaseUnityPlugin)this).Config.Bind("General", "Logging", false, "Enable Lobby Filter informational logging. Warnings are still logged when this is false."); ((BaseUnityPlugin)this).Config.Save(); LogInfo($"Config loaded: PollInterval={PollInterval.Value:0.###}s, InvalidRemoveDelay={InvalidRemoveDelay.Value:0.###}s."); } private static void RegisterStaticCallbacks() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown if (!callbacksRegistered) { callbacksRegistered = true; RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; CameraCallback onPreCull = Camera.onPreCull; object obj = <>O.<1>__OnCameraPreCull; if (obj == null) { CameraCallback val = OnCameraPreCull; <>O.<1>__OnCameraPreCull = val; obj = (object)val; } Camera.onPreCull = (CameraCallback)Delegate.Combine((Delegate?)(object)onPreCull, (Delegate?)obj); Application.quitting += OnApplicationQuitting; } } private static void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { ProcessFrame(); } private static void OnCameraPreCull(Camera camera) { ProcessFrame(); } private static void ProcessFrame() { int frameCount = Time.frameCount; if (frameCount != lastProcessedFrame) { lastProcessedFrame = frameCount; StableLobbyManager.Tick(); } } private void OnDestroy() { LogInfo("Plugin OnDestroy called; stable lobby cache remains active."); } private static void OnApplicationQuitting() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown if (callbacksRegistered) { RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering; CameraCallback onPreCull = Camera.onPreCull; object obj = <>O.<1>__OnCameraPreCull; if (obj == null) { CameraCallback val = OnCameraPreCull; <>O.<1>__OnCameraPreCull = val; obj = (object)val; } Camera.onPreCull = (CameraCallback)Delegate.Remove((Delegate?)(object)onPreCull, (Delegate?)obj); Application.quitting -= OnApplicationQuitting; callbacksRegistered = false; } StableLobbyManager.Shutdown(); } internal static void LogInfo(string message) { if (LoggingEnabled) { ManualLogSource modLog = ModLog; if (modLog != null) { modLog.LogInfo((object)message); } } } internal static void LogWarning(string message) { ManualLogSource modLog = ModLog; if (modLog != null) { modLog.LogWarning((object)message); } } } internal sealed class LobbyState { internal ulong LobbyId; internal Lobby Lobby; internal string Name; internal bool Invalid; internal string Reason; internal float LastSeenTime; internal int ConsecutiveMisses; internal float InvalidSince = -1f; internal bool Removed; internal LobbySlot Slot; internal int CachedSlotInstanceId; internal Graphic[] CachedGraphics; internal Button CachedJoinButton; internal Image CachedInvalidOverlay; internal bool HasOriginalJoinInteractable; internal bool OriginalJoinInteractable; internal bool UnicodeFallbackApplied; internal bool VisualStateInitialized; internal bool AppliedInvalid; internal string AppliedName; internal int AppliedMemberCount = int.MinValue; internal int AppliedMaxMembers = int.MinValue; } internal static class StableLobbyManager { private const float DefaultBackgroundQueryInterval = 0.125f; private const float DefaultInvalidRemoveDelay = 30f; private const int MissesBeforeStaleMarker = 3; private const float SlotSpacing = 42f; private static readonly Dictionary States = new Dictionary(); private static readonly HashSet SeenScratch = new HashSet(); private static readonly HashSet VanillaLobbyIds = new HashSet(); private static readonly Dictionary ExistingSlotsScratch = new Dictionary(); private static readonly List ReflowScratch = new List(); private static SteamLobbyManager cachedManager; private static int cachedContainerChildCount = -1; private static bool uiScanRequested = true; private const float VanillaUiSettleDelay = 0.5f; private const string AppendedRowPrefix = "LobbyFilterAppended_"; private static bool hasObservedVanillaLobbyList; private static float vanillaUiSettlingUntil = -1f; private static float nextQueryTime; private static bool queryRunning; private static bool loggedBrowserDetected; private const int MaxUnicodeFallbackFailures = 3; private const string InvalidOverlayName = "LobbyFilterInvalidOverlay"; private static TMP_FontAsset unicodeFallbackFont; private static Font unicodeFallbackSourceFont; private static int unicodeFallbackFailureCount; private static bool unicodeFallbackDisabled; internal static void Tick() { SteamLobbyManager val = cachedManager; if ((Object)(object)val == (Object)null) { val = Object.FindObjectOfType(); if ((Object)(object)val != (Object)null) { cachedManager = val; uiScanRequested = true; } } if ((Object)(object)val == (Object)null || (Object)(object)val.levelListContainer == (Object)null || !((Component)val.levelListContainer).gameObject.activeInHierarchy) { return; } if (!loggedBrowserDetected) { loggedBrowserDetected = true; Plugin.LogInfo("Steam lobby browser detected; background cache refresh active."); } int childCount = val.levelListContainer.childCount; if (uiScanRequested || childCount != cachedContainerChildCount) { ApplyKnownStatesToExistingSlots(val); cachedContainerChildCount = val.levelListContainer.childCount; uiScanRequested = false; } if (!(Time.unscaledTime < nextQueryTime)) { float num = ((Plugin.PollInterval == null) ? 0.125f : Mathf.Max(0.01f, Plugin.PollInterval.Value)); nextQueryTime = Time.unscaledTime + num; if (!queryRunning && !((Object)(object)GameNetworkManager.Instance == (Object)null) && !GameNetworkManager.Instance.waitingForLobbyDataRefresh) { RunBackgroundQuery(val); } } } private static async void RunBackgroundQuery(SteamLobbyManager manager) { queryRunning = true; try { LobbyQuery val = SteamMatchmaking.LobbyList; LobbyQuery val2; switch (manager.sortByDistanceSetting) { case 0: val2 = ((LobbyQuery)(ref val)).FilterDistanceClose(); val = ((LobbyQuery)(ref val2)).WithKeyValue("vers", GameNetworkManager.Instance.gameVersionNum.ToString()); break; case 1: val2 = ((LobbyQuery)(ref val)).FilterDistanceFar(); val = ((LobbyQuery)(ref val2)).WithKeyValue("vers", GameNetworkManager.Instance.gameVersionNum.ToString()); break; default: val2 = ((LobbyQuery)(ref val)).FilterDistanceWorldwide(); val = ((LobbyQuery)(ref val2)).WithKeyValue("vers", GameNetworkManager.Instance.gameVersionNum.ToString()); break; } string text = (((Object)(object)manager.serverTagInputField == (Object)null) ? "" : manager.serverTagInputField.text); val = ((LobbyQuery)(ref val)).WithKeyValue("tag", string.IsNullOrEmpty(text) ? "none" : text.Substring(0, Mathf.Min(19, text.Length)).ToLower()); Lobby[] array = await ((LobbyQuery)(ref val)).RequestAsync(); if (array != null) { ProcessSnapshot(manager, array); } } catch (Exception ex) { Plugin.LogWarning("Background lobby query failed: " + ex.Message); } finally { queryRunning = false; } } internal static void ObserveVanillaLobbyList(Lobby[] lobbies) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (lobbies == null) { return; } float unscaledTime = Time.unscaledTime; hasObservedVanillaLobbyList = true; vanillaUiSettlingUntil = unscaledTime + 0.5f; VanillaLobbyIds.Clear(); for (int i = 0; i < lobbies.Length; i++) { Lobby lobby = lobbies[i]; ulong num = SteamId.op_Implicit(((Lobby)(ref lobby)).Id); if (num == 0L) { continue; } VanillaLobbyIds.Add(num); if (!States.TryGetValue(num, out var value)) { value = new LobbyState { LobbyId = num }; States.Add(num, value); } bool invalid = value.Invalid; string reason = value.Reason; float invalidSince = value.InvalidSince; bool removed = value.Removed; value.Lobby = lobby; value.Name = SafeLobbyName(lobby); EvaluateLobby(lobby, value); if (invalid && !value.Invalid) { value.Invalid = true; value.Reason = reason; value.InvalidSince = invalidSince; value.Removed = removed; if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Vanilla refresh preserved invalid state for lobby {num}: {reason}"); } } else if (value.Invalid && (!invalid || reason != value.Reason)) { Plugin.LogInfo($"Marked visible lobby {num} invalid immediately: {value.Reason}"); } } } private static void ProcessSnapshot(SteamLobbyManager manager, Lobby[] lobbies) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) float unscaledTime = Time.unscaledTime; HashSet seenScratch = SeenScratch; seenScratch.Clear(); for (int i = 0; i < lobbies.Length; i++) { Lobby lobby = lobbies[i]; ulong num = SteamId.op_Implicit(((Lobby)(ref lobby)).Id); if (num == 0L) { continue; } seenScratch.Add(num); if (!States.TryGetValue(num, out var value)) { value = new LobbyState { LobbyId = num }; States.Add(num, value); } bool invalid = value.Invalid; string reason = value.Reason; value.Lobby = lobby; value.Name = SafeLobbyName(lobby); value.LastSeenTime = unscaledTime; value.ConsecutiveMisses = 0; EvaluateLobby(lobby, value); if (value.Invalid && (!invalid || reason != value.Reason)) { if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Marked lobby {num} invalid: {value.Reason}"); } } else if (!value.Invalid && invalid && Plugin.LoggingEnabled) { Plugin.LogInfo($"Lobby {num} became valid again; restored row."); } } foreach (KeyValuePair state in States) { LobbyState value2 = state.Value; if (seenScratch.Contains(value2.LobbyId)) { continue; } value2.ConsecutiveMisses++; if (value2.ConsecutiveMisses >= 3 && (!value2.Invalid || value2.Reason != "No longer returned by Steam")) { SetInvalid(value2, "No longer returned by Steam"); if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Marked lobby {value2.LobbyId} invalid: {value2.Reason}"); } } } ApplySnapshotToUi(manager, lobbies); } private static void EvaluateLobby(Lobby lobby, LobbyState state) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(state.Name)) { SetInvalid(state, "Lobby has no name"); return; } string text = SafeGetData(lobby, "vers"); if (!string.IsNullOrEmpty(text) && (Object)(object)GameNetworkManager.Instance != (Object)null && text != GameNetworkManager.Instance.gameVersionNum.ToString()) { SetInvalid(state, "Wrong game version (" + text + ")"); } else if (string.Equals(SafeGetData(lobby, "joinable"), "false", StringComparison.OrdinalIgnoreCase)) { SetInvalid(state, "Host has already landed / lobby not joinable"); } else if (((Lobby)(ref lobby)).MemberCount < 1) { SetInvalid(state, "Lobby has no members"); } else if (((Lobby)(ref lobby)).MemberCount >= ((Lobby)(ref lobby)).MaxMembers) { SetInvalid(state, "Lobby is full"); } else { SetValid(state); } } private static void SetValid(LobbyState state) { state.Invalid = false; state.Reason = ""; state.InvalidSince = -1f; state.Removed = false; } private static void SetInvalid(LobbyState state, string reason) { if (!state.Invalid || state.InvalidSince < 0f) { state.InvalidSince = Time.unscaledTime; } state.Invalid = true; state.Reason = reason; } private static float GetInvalidRemoveDelay() { if (Plugin.InvalidRemoveDelay == null) { return 30f; } return Mathf.Max(0f, Plugin.InvalidRemoveDelay.Value); } private static bool ShouldRemove(LobbyState state) { if (state != null && state.Invalid && state.InvalidSince >= 0f) { return Time.unscaledTime - state.InvalidSince >= GetInvalidRemoveDelay(); } return false; } private static string SafeLobbyName(Lobby lobby) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return SafeGetData(lobby, "name") ?? ""; } private static string SafeGetData(Lobby lobby, string key) { try { return ((Lobby)(ref lobby)).GetData(key) ?? ""; } catch { return ""; } } private static void ApplySnapshotToUi(SteamLobbyManager manager, Lobby[] snapshot) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Dictionary existingSlots = GetExistingSlots(manager); foreach (KeyValuePair item in existingSlots) { if (States.TryGetValue(item.Key, out var value)) { UpdateSlot(item.Value, value); } } bool flag = hasObservedVanillaLobbyList && Time.unscaledTime >= vanillaUiSettlingUntil; for (int i = 0; i < snapshot.Length; i++) { Lobby val = snapshot[i]; ulong num = SteamId.op_Implicit(((Lobby)(ref val)).Id); if (num != 0L && !existingSlots.ContainsKey(num) && !VanillaLobbyIds.Contains(num) && flag && States.TryGetValue(num, out var value2) && (!value2.Removed || !value2.Invalid)) { LobbySlot val2 = CreateSlot(manager, value2, existingSlots); if ((Object)(object)val2 != (Object)null) { existingSlots[num] = val2; } } } if (!((Object)(object)manager.serverListBlankText != (Object)null)) { return; } int num2 = 0; foreach (KeyValuePair item2 in existingSlots) { LobbySlot value3 = item2.Value; if ((Object)(object)value3 != (Object)null && ((Component)value3).gameObject.activeInHierarchy) { num2++; } } ((TMP_Text)manager.serverListBlankText).text = ((num2 == 0) ? "No available servers to join." : ""); } private static bool IsLobbyFilterAppendedRow(Transform rowRoot) { if ((Object)(object)rowRoot != (Object)null && ((Object)((Component)rowRoot).gameObject).name != null) { return ((Object)((Component)rowRoot).gameObject).name.StartsWith("LobbyFilterAppended_", StringComparison.Ordinal); } return false; } private static ulong GetLobbyId(LobbySlot slot) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)slot == (Object)null) { return 0uL; } ulong num = SteamId.op_Implicit(((Lobby)(ref slot.thisLobby)).Id); if (num == 0L) { num = SteamId.op_Implicit(slot.lobbyId); } return num; } private static Transform GetLobbyRowRoot(LobbySlot slot, Transform container) { if ((Object)(object)slot == (Object)null) { return null; } Transform val = ((Component)slot).transform; if ((Object)(object)container == (Object)null) { return val; } while ((Object)(object)val.parent != (Object)null && (Object)(object)val.parent != (Object)(object)container) { val = val.parent; } return val; } private static RectTransform GetLobbyRowRect(LobbySlot slot, Transform container) { Transform lobbyRowRoot = GetLobbyRowRoot(slot, container); if (!((Object)(object)lobbyRowRoot == (Object)null)) { return (RectTransform)(object)((lobbyRowRoot is RectTransform) ? lobbyRowRoot : null); } return null; } private static void DestroyLobbyRowRoot(LobbySlot slot, SteamLobbyManager manager) { if (!((Object)(object)slot == (Object)null) && !((Object)(object)manager == (Object)null) && !((Object)(object)manager.levelListContainer == (Object)null)) { Transform lobbyRowRoot = GetLobbyRowRoot(slot, manager.levelListContainer); if (!((Object)(object)lobbyRowRoot == (Object)null)) { ((Component)lobbyRowRoot).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)lobbyRowRoot).gameObject); } } } private static Dictionary GetExistingSlots(SteamLobbyManager manager) { Dictionary existingSlotsScratch = ExistingSlotsScratch; existingSlotsScratch.Clear(); if ((Object)(object)manager == (Object)null || (Object)(object)manager.levelListContainer == (Object)null) { return existingSlotsScratch; } int childCount = manager.levelListContainer.childCount; if (!uiScanRequested && childCount == cachedContainerChildCount) { foreach (KeyValuePair state in States) { LobbyState value = state.Value; if (value != null && (Object)(object)value.Slot != (Object)null && !value.Removed) { existingSlotsScratch[state.Key] = value.Slot; } } if (existingSlotsScratch.Count > 0) { return existingSlotsScratch; } } existingSlotsScratch.Clear(); LobbySlot[] componentsInChildren = ((Component)manager.levelListContainer).GetComponentsInChildren(true); bool flag = false; foreach (LobbySlot val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Transform lobbyRowRoot = GetLobbyRowRoot(val, manager.levelListContainer); if ((Object)(object)lobbyRowRoot == (Object)null || !((Component)lobbyRowRoot).gameObject.activeSelf) { continue; } ulong lobbyId = GetLobbyId(val); if (lobbyId == 0L) { continue; } LobbySlot value3; if (States.TryGetValue(lobbyId, out var value2) && value2.Removed && value2.Invalid) { DestroyLobbyRowRoot(val, manager); flag = true; } else if (existingSlotsScratch.TryGetValue(lobbyId, out value3)) { Transform lobbyRowRoot2 = GetLobbyRowRoot(value3, manager.levelListContainer); if (!((Object)(object)lobbyRowRoot2 != (Object)(object)lobbyRowRoot)) { continue; } bool flag2 = IsLobbyFilterAppendedRow(lobbyRowRoot2); bool flag3 = IsLobbyFilterAppendedRow(lobbyRowRoot); if (flag2 && !flag3) { DestroyLobbyRowRoot(value3, manager); existingSlotsScratch[lobbyId] = val; if (States.TryGetValue(lobbyId, out var value4)) { AttachSlot(value4, val); } flag = true; if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Replaced appended duplicate with vanilla row for {lobbyId}."); } } else if (!flag2 && flag3) { DestroyLobbyRowRoot(val, manager); flag = true; if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Removed appended duplicate row for {lobbyId}."); } } else { DestroyLobbyRowRoot(val, manager); flag = true; if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Removed same-owner duplicate row for {lobbyId}."); } } } else { existingSlotsScratch[lobbyId] = val; if (States.TryGetValue(lobbyId, out value2)) { AttachSlot(value2, val); } } } if (flag) { cachedContainerChildCount = -1; uiScanRequested = true; } else { cachedContainerChildCount = manager.levelListContainer.childCount; uiScanRequested = false; } return existingSlotsScratch; } private static void ApplyKnownStatesToExistingSlots(SteamLobbyManager manager) { foreach (KeyValuePair existingSlot in GetExistingSlots(manager)) { if (States.TryGetValue(existingSlot.Key, out var value)) { UpdateSlot(existingSlot.Value, value); } } } private static LobbySlot CreateSlot(SteamLobbyManager manager, LobbyState state, Dictionary existing) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) try { GameObject val = ((SafeGetData(state.Lobby, "chal") == "t") ? manager.LobbySlotPrefabChallenge : manager.LobbySlotPrefab); if ((Object)(object)val == (Object)null) { return null; } GameObject val2 = Object.Instantiate(val, manager.levelListContainer); ((Object)val2).name = "LobbyFilterAppended_" + state.LobbyId; RectTransform component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { component.anchoredPosition = new Vector2(0f, GetNextSlotY(existing)); } LobbySlot componentInChildren = val2.GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Object.Destroy((Object)(object)val2); return null; } componentInChildren.lobbyId = ((Lobby)(ref state.Lobby)).Id; componentInChildren.thisLobby = state.Lobby; UpdateSlot(componentInChildren, state); Plugin.LogInfo($"Added new lobby slot without rebuilding list: {state.LobbyId}"); return componentInChildren; } catch (Exception ex) { Plugin.LogWarning($"Could not append lobby {state.LobbyId}: {ex.Message}"); return null; } } private static float GetNextSlotY(Dictionary existing) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) bool flag = false; float num = 0f; foreach (KeyValuePair item in existing) { LobbySlot value = item.Value; if ((Object)(object)value == (Object)null) { continue; } RectTransform val = ((Component)value).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)value).GetComponentInParent(); } if (!((Object)(object)val == (Object)null)) { float y = val.anchoredPosition.y; if (!flag || y < num) { num = y; flag = true; } } } if (!flag) { return 0f; } return num - 42f; } private static void AttachSlot(LobbyState state, LobbySlot slot) { if (state != null && !((Object)(object)slot == (Object)null)) { int instanceID = ((Object)slot).GetInstanceID(); if (!((Object)(object)state.Slot == (Object)(object)slot) || state.CachedSlotInstanceId != instanceID) { ReleaseUiCache(state); state.Slot = slot; state.CachedSlotInstanceId = instanceID; state.CachedGraphics = ((Component)slot).GetComponentsInChildren(true); state.CachedJoinButton = FindJoinButton(slot); state.HasOriginalJoinInteractable = false; state.OriginalJoinInteractable = true; CacheAndCleanInvalidOverlays(state); state.UnicodeFallbackApplied = false; state.VisualStateInitialized = false; state.AppliedName = null; state.AppliedMemberCount = int.MinValue; state.AppliedMaxMembers = int.MinValue; } } } private static void ReleaseUiCache(LobbyState state) { if (state != null) { state.CachedGraphics = null; state.CachedJoinButton = null; state.CachedInvalidOverlay = null; state.HasOriginalJoinInteractable = false; state.OriginalJoinInteractable = true; state.CachedSlotInstanceId = 0; state.UnicodeFallbackApplied = false; state.VisualStateInitialized = false; state.AppliedName = null; state.AppliedMemberCount = int.MinValue; state.AppliedMaxMembers = int.MinValue; } } private static void CacheAndCleanInvalidOverlays(LobbyState state) { if (state == null || (Object)(object)state.Slot == (Object)null) { return; } SteamLobbyManager val = cachedManager; Transform container = (((Object)(object)val == (Object)null) ? null : val.levelListContainer); Transform lobbyRowRoot = GetLobbyRowRoot(state.Slot, container); if ((Object)(object)lobbyRowRoot == (Object)null) { return; } Image val2 = null; Image[] componentsInChildren = ((Component)lobbyRowRoot).GetComponentsInChildren(true); foreach (Image val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null || ((Object)((Component)val3).gameObject).name != "LobbyFilterInvalidOverlay") { continue; } if ((Object)(object)val2 == (Object)null) { val2 = val3; continue; } ((Component)val3).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)val3).gameObject); if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Removed duplicate invalid overlay from lobby {state.LobbyId}."); } } state.CachedInvalidOverlay = val2; } private static void UpdateSlot(LobbySlot slot, LobbyState state) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)slot == (Object)null || state == null) { return; } AttachSlot(state, slot); if (ShouldRemove(state)) { if (!state.Removed) { state.Removed = true; Plugin.LogInfo($"Removing lobby {state.LobbyId} after {GetInvalidRemoveDelay():0.###} seconds continuously invalid: {state.Reason}"); RemoveLobbyRowAndReflow(state, slot); } return; } slot.lobbyId = ((Lobby)(ref state.Lobby)).Id; slot.thisLobby = state.Lobby; string text = state.Name ?? ""; if (text.Length > 40) { text = text.Substring(0, 40); } if (!state.UnicodeFallbackApplied) { ApplyUnicodeFallbacks(state); state.UnicodeFallbackApplied = true; } if ((Object)(object)slot.LobbyName != (Object)null && state.AppliedName != text) { ((TMP_Text)slot.LobbyName).text = text; state.AppliedName = text; } int memberCount = ((Lobby)(ref state.Lobby)).MemberCount; int maxMembers = ((Lobby)(ref state.Lobby)).MaxMembers; if ((Object)(object)slot.playerCount != (Object)null && (state.AppliedMemberCount != memberCount || state.AppliedMaxMembers != maxMembers)) { ((TMP_Text)slot.playerCount).text = $"{memberCount} / {maxMembers}"; state.AppliedMemberCount = memberCount; state.AppliedMaxMembers = maxMembers; } if (!state.VisualStateInitialized || state.AppliedInvalid != state.Invalid) { ApplyDisabledVisualState(state, state.Invalid, state.LobbyId, state.Reason); state.AppliedInvalid = state.Invalid; state.VisualStateInitialized = true; if (Plugin.LoggingEnabled && state.Invalid) { Plugin.LogInfo($"Applied red invalid state to lobby {state.LobbyId}; rowActive=" + $"{((Component)slot).gameObject.activeInHierarchy}, overlay=" + (((Object)(object)state.CachedInvalidOverlay != (Object)null) ? "ready" : "missing") + "."); } } } private static void ApplyUnicodeFallbacks(LobbyState state) { if (state == null || state.CachedGraphics == null) { return; } TMP_FontAsset val = GetUnicodeFallbackFont(); if ((Object)(object)val == (Object)null) { return; } for (int i = 0; i < state.CachedGraphics.Length; i++) { Graphic obj = state.CachedGraphics[i]; TMP_Text val2 = (TMP_Text)(object)((obj is TMP_Text) ? obj : null); if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.font == (Object)null)) { List list = val2.font.fallbackFontAssetTable; if (list == null) { list = new List(); val2.font.fallbackFontAssetTable = list; } if (!list.Contains(val)) { list.Add(val); ((Graphic)val2).SetAllDirty(); } } } } private static TMP_FontAsset GetUnicodeFallbackFont() { if ((Object)(object)unicodeFallbackFont != (Object)null) { return unicodeFallbackFont; } if (unicodeFallbackDisabled) { return null; } try { TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll(); foreach (TMP_FontAsset val in array) { if (!((Object)(object)val == (Object)null) && !(((Object)val).name == "3270-Regular SDF") && val.HasCharacter(21941)) { unicodeFallbackFont = val; Plugin.LogInfo("Using loaded TMP Unicode fallback font: " + ((Object)val).name); return unicodeFallbackFont; } } } catch (Exception ex) { if (Plugin.LoggingEnabled) { Plugin.LogInfo("Loaded TMP fallback search failed: " + ex.Message); } } string[] array2 = new string[5] { "Microsoft YaHei", "Microsoft JhengHei", "Meiryo", "Yu Gothic UI", "Malgun Gothic" }; for (int j = 0; j < array2.Length; j++) { if (unicodeFallbackFailureCount >= 3) { break; } string text = array2[j]; Font val2 = null; try { val2 = Font.CreateDynamicFontFromOSFont(text, 32); if ((Object)(object)val2 == (Object)null) { RecordUnicodeFallbackFailure(text, "Unity returned no OS font."); continue; } TMP_FontAsset val3 = TMP_FontAsset.CreateFontAsset(val2); if ((Object)(object)val3 == (Object)null) { Object.Destroy((Object)(object)val2); RecordUnicodeFallbackFailure(text, "TextMeshPro could not create a font asset."); continue; } ((Object)val3).name = "LobbyFilterUnicodeFallback"; val3.isMultiAtlasTexturesEnabled = true; unicodeFallbackSourceFont = val2; unicodeFallbackFont = val3; Plugin.LogInfo("Unicode lobby-font fallback enabled using " + text + "."); return unicodeFallbackFont; } catch (Exception ex2) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } RecordUnicodeFallbackFailure(text, ex2.Message); } } if (unicodeFallbackFailureCount >= 3) { DisableUnicodeFallbackForSession(); } return null; } private static void RecordUnicodeFallbackFailure(string fontName, string reason) { unicodeFallbackFailureCount++; if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Unicode fallback attempt {unicodeFallbackFailureCount}/{3} failed for {fontName}: {reason}"); } if (unicodeFallbackFailureCount >= 3) { DisableUnicodeFallbackForSession(); } } private static void DisableUnicodeFallbackForSession() { if (!unicodeFallbackDisabled) { unicodeFallbackDisabled = true; Plugin.LogWarning($"Unicode font fallback disabled for this session after {unicodeFallbackFailureCount} failed attempts."); } } private static void ApplyDisabledVisualState(LobbyState state, bool disabled, ulong lobbyId, string reason) { if (state == null || (Object)(object)state.Slot == (Object)null) { return; } Transform val = ((Component)state.Slot).transform.Find("LobbyPrecheckXMarker"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } Button cachedJoinButton = state.CachedJoinButton; if ((Object)(object)cachedJoinButton != (Object)null) { if (!state.HasOriginalJoinInteractable) { state.OriginalJoinInteractable = ((Selectable)cachedJoinButton).interactable; state.HasOriginalJoinInteractable = true; } ((Selectable)cachedJoinButton).interactable = !disabled && state.OriginalJoinInteractable; } else if (disabled) { Plugin.LogWarning($"Could not find JoinButton for lobby {lobbyId}."); } SetInvalidOverlay(state, disabled); } private static void SetInvalidOverlay(LobbyState state, bool visible) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (state == null || (Object)(object)state.Slot == (Object)null) { return; } SteamLobbyManager val = cachedManager; Transform container = (((Object)(object)val == (Object)null) ? null : val.levelListContainer); Transform lobbyRowRoot = GetLobbyRowRoot(state.Slot, container); RectTransform val2 = (RectTransform)(object)((lobbyRowRoot is RectTransform) ? lobbyRowRoot : null); if ((Object)(object)val2 == (Object)null) { return; } CacheAndCleanInvalidOverlays(state); Image val3 = state.CachedInvalidOverlay; if ((Object)(object)val3 == (Object)null && visible) { GameObject val4 = new GameObject("LobbyFilterInvalidOverlay"); val4.transform.SetParent((Transform)(object)val2, false); val3 = (state.CachedInvalidOverlay = val4.AddComponent()); RectTransform rectTransform = ((Graphic)val3).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; ((Graphic)val3).raycastTarget = false; if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Created invalid overlay for lobby {state.LobbyId}."); } } if (!((Object)(object)val3 == (Object)null)) { ((Graphic)val3).color = new Color(0.95f, 0.08f, 0.08f, 0.18f); ((Graphic)val3).raycastTarget = false; ((Component)val3).gameObject.SetActive(visible); if (visible) { ((Component)val3).transform.SetAsLastSibling(); } } } private static Button FindJoinButton(LobbySlot slot) { Transform val = ((Component)slot).transform.Find("JoinButton"); if ((Object)(object)val != (Object)null) { Button component = ((Component)val).GetComponent