Decompiled source of LobbyFilter v1.0.0

BepInEx/plugins/LobbyFilter/LobbyFilter.dll

Decompiled 3 hours ago
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<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.");
		}
	}
}