Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LobbyFilter v1.0.0
BepInEx/plugins/LobbyFilter/LobbyFilter.dll
Decompiled 3 hours agousing 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<ScriptableRenderContext, Camera> <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<float> PollInterval; internal static ConfigEntry<float> InvalidRemoveDelay; internal static ConfigEntry<bool> 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<float>("General", "PollInterval", 0.125f, "Seconds between updates. Lower values update faster."); InvalidRemoveDelay = ((BaseUnityPlugin)this).Config.Bind<float>("General", "InvalidRemoveDelay", 30f, "Seconds a lobby must remain continuously unjoinable before its removed."); Logging = ((BaseUnityPlugin)this).Config.Bind<bool>("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<ulong, LobbyState> States = new Dictionary<ulong, LobbyState>(); private static readonly HashSet<ulong> SeenScratch = new HashSet<ulong>(); private static readonly HashSet<ulong> VanillaLobbyIds = new HashSet<ulong>(); private static readonly Dictionary<ulong, LobbySlot> ExistingSlotsScratch = new Dictionary<ulong, LobbySlot>(); private static readonly List<LobbySlot> ReflowScratch = new List<LobbySlot>(); 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<SteamLobbyManager>(); 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<ulong> 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<ulong, LobbyState> 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<ulong, LobbySlot> existingSlots = GetExistingSlots(manager); foreach (KeyValuePair<ulong, LobbySlot> 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<ulong, LobbySlot> 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<ulong, LobbySlot> GetExistingSlots(SteamLobbyManager manager) { Dictionary<ulong, LobbySlot> 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<ulong, LobbyState> 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<LobbySlot>(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<ulong, LobbySlot> 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<ulong, LobbySlot> 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<GameObject>(val, manager.levelListContainer); ((Object)val2).name = "LobbyFilterAppended_" + state.LobbyId; RectTransform component = val2.GetComponent<RectTransform>(); if ((Object)(object)component != (Object)null) { component.anchoredPosition = new Vector2(0f, GetNextSlotY(existing)); } LobbySlot componentInChildren = val2.GetComponentInChildren<LobbySlot>(); 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<ulong, LobbySlot> existing) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) bool flag = false; float num = 0f; foreach (KeyValuePair<ulong, LobbySlot> item in existing) { LobbySlot value = item.Value; if ((Object)(object)value == (Object)null) { continue; } RectTransform val = ((Component)value).GetComponent<RectTransform>(); if ((Object)(object)val == (Object)null) { val = ((Component)value).GetComponentInParent<RectTransform>(); } 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<Graphic>(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<Image>(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<TMP_FontAsset> list = val2.font.fallbackFontAssetTable; if (list == null) { list = new List<TMP_FontAsset>(); 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<TMP_FontAsset>(); 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<Image>()); 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<Button>(); if ((Object)(object)component != (Object)null) { return component; } } Button[] componentsInChildren = ((Component)slot).GetComponentsInChildren<Button>(true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null && ((Object)((Component)componentsInChildren[i]).gameObject).name == "JoinButton") { return componentsInChildren[i]; } } if (componentsInChildren.Length == 0) { return null; } return componentsInChildren[0]; } private static void RemoveLobbyRowAndReflow(LobbyState state, LobbySlot slot) { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if (state == null) { return; } SteamLobbyManager val = cachedManager; if ((Object)(object)val == (Object)null) { val = (cachedManager = Object.FindObjectOfType<SteamLobbyManager>()); } if ((Object)(object)val == (Object)null || (Object)(object)val.levelListContainer == (Object)null) { return; } float num = 0f; bool flag = false; int num2 = 0; HashSet<int> hashSet = new HashSet<int>(); LobbySlot[] componentsInChildren = ((Component)val.levelListContainer).GetComponentsInChildren<LobbySlot>(true); foreach (LobbySlot val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || GetLobbyId(val2) != state.LobbyId) { continue; } Transform lobbyRowRoot = GetLobbyRowRoot(val2, val.levelListContainer); if ((Object)(object)lobbyRowRoot == (Object)null) { continue; } int instanceID = ((Object)lobbyRowRoot).GetInstanceID(); if (!hashSet.Add(instanceID)) { continue; } Button val3 = FindJoinButton(val2); if ((Object)(object)val3 != (Object)null) { ((Selectable)val3).interactable = false; ((Component)val3).gameObject.SetActive(false); } RectTransform val4 = (RectTransform)(object)((lobbyRowRoot is RectTransform) ? lobbyRowRoot : null); if ((Object)(object)val4 != (Object)null) { float y = val4.anchoredPosition.y; if (!flag || y > num) { num = y; flag = true; } } ((Component)lobbyRowRoot).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)lobbyRowRoot).gameObject); num2++; } ReleaseUiCache(state); state.Slot = null; VanillaLobbyIds.Remove(state.LobbyId); cachedContainerChildCount = -1; uiScanRequested = true; ReflowLobbySlots(flag, num); if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Removed {num2} complete lobby row(s) for {state.LobbyId}, including any duplicates."); } } private static void ReflowLobbySlots(bool hasRemovedY, float removedY) { //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) SteamLobbyManager manager = cachedManager; if ((Object)(object)manager == (Object)null) { manager = Object.FindObjectOfType<SteamLobbyManager>(); cachedManager = manager; } if ((Object)(object)manager == (Object)null || (Object)(object)manager.levelListContainer == (Object)null) { return; } LobbySlot[] componentsInChildren = ((Component)manager.levelListContainer).GetComponentsInChildren<LobbySlot>(false); List<LobbySlot> reflowScratch = ReflowScratch; reflowScratch.Clear(); HashSet<int> hashSet = new HashSet<int>(); 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; } int instanceID = ((Object)lobbyRowRoot).GetInstanceID(); if (hashSet.Add(instanceID)) { ulong lobbyId = GetLobbyId(val); if (lobbyId == 0L || !States.TryGetValue(lobbyId, out var value) || !value.Removed) { reflowScratch.Add(val); } } } if (reflowScratch.Count == 0) { return; } reflowScratch.Sort(delegate(LobbySlot a, LobbySlot b) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) RectTransform lobbyRowRect3 = GetLobbyRowRect(a, manager.levelListContainer); RectTransform lobbyRowRect4 = GetLobbyRowRect(b, manager.levelListContainer); float value2 = (((Object)(object)lobbyRowRect3 == (Object)null) ? 0f : lobbyRowRect3.anchoredPosition.y); return (((Object)(object)lobbyRowRect4 == (Object)null) ? 0f : lobbyRowRect4.anchoredPosition.y).CompareTo(value2); }); RectTransform lobbyRowRect = GetLobbyRowRect(reflowScratch[0], manager.levelListContainer); float num = (((Object)(object)lobbyRowRect == (Object)null) ? 0f : lobbyRowRect.anchoredPosition.y); if (hasRemovedY && removedY > num) { num = removedY; } for (int num2 = 0; num2 < reflowScratch.Count; num2++) { RectTransform lobbyRowRect2 = GetLobbyRowRect(reflowScratch[num2], manager.levelListContainer); if (!((Object)(object)lobbyRowRect2 == (Object)null)) { Vector2 anchoredPosition = lobbyRowRect2.anchoredPosition; anchoredPosition.y = num - 42f * (float)num2; lobbyRowRect2.anchoredPosition = anchoredPosition; } } if (Plugin.LoggingEnabled) { Plugin.LogInfo($"Repositioned {reflowScratch.Count} complete lobby rows after removal."); } } internal static void NotifyVanillaUiChanged(SteamLobbyManager manager) { if ((Object)(object)manager != (Object)null) { cachedManager = manager; } vanillaUiSettlingUntil = Mathf.Max(vanillaUiSettlingUntil, Time.unscaledTime + 0.5f); uiScanRequested = true; } internal static void Shutdown() { foreach (KeyValuePair<ulong, LobbyState> state in States) { ReleaseUiCache(state.Value); } States.Clear(); SeenScratch.Clear(); VanillaLobbyIds.Clear(); ExistingSlotsScratch.Clear(); ReflowScratch.Clear(); cachedManager = null; cachedContainerChildCount = -1; uiScanRequested = true; hasObservedVanillaLobbyList = false; vanillaUiSettlingUntil = -1f; if ((Object)(object)unicodeFallbackFont != (Object)null && ((Object)unicodeFallbackFont).name == "LobbyFilterUnicodeFallback") { Object.Destroy((Object)(object)unicodeFallbackFont); } if ((Object)(object)unicodeFallbackSourceFont != (Object)null) { Object.Destroy((Object)(object)unicodeFallbackSourceFont); } unicodeFallbackFont = null; unicodeFallbackSourceFont = null; unicodeFallbackFailureCount = 0; unicodeFallbackDisabled = false; queryRunning = false; loggedBrowserDetected = false; } } [HarmonyPatch(typeof(SteamLobbyManager), "loadLobbyListAndFilter")] internal static class VanillaLobbyObservationPatch { [HarmonyPrefix] private static void Prefix(Lobby[] lobbyList) { StableLobbyManager.ObserveVanillaLobbyList(lobbyList); } [HarmonyPostfix] private static void Postfix(SteamLobbyManager __instance) { StableLobbyManager.NotifyVanillaUiChanged(__instance); } } [HarmonyPatch(typeof(SteamLobbyManager), "LoadServerList")] internal static class LoadServerListPatch { [HarmonyPostfix] private static void Postfix(SteamLobbyManager __instance) { StableLobbyManager.NotifyVanillaUiChanged(__instance); Plugin.LogInfo("Vanilla/manual lobby list rebuild requested."); } } }