Decompiled source of Room Hub v3.1.1

RoomHub.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PEAKLib.UI;
using PEAKLib.UI.Elements;
using Peak.Network;
using Photon.Pun;
using Photon.Realtime;
using RoomHub.Ui;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Zorro.Core;
using Zorro.UI.Modal;

[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("RoomHub")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+a4772869b5f134b254d4385335d401de87f72dfa")]
[assembly: AssemblyProduct("RoomHub")]
[assembly: AssemblyTitle("RoomHub")]
[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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 RoomHub
{
	public enum FriendGameMode
	{
		Joinable,
		InviteOnly,
		Solo,
		MainMenu,
		Airport,
		Unknown
	}
	public enum FriendArea
	{
		None,
		MainMenu,
		Airport,
		Shore,
		Tropics,
		Alpine,
		Mesa,
		Caldera,
		Kiln,
		Peak,
		Roots,
		Gloom,
		Citadel,
		Nadir
	}
	public readonly struct FriendRoomInfo
	{
		public readonly ulong SteamId;

		public readonly string Name;

		public readonly ulong LobbyId;

		public readonly FriendGameMode Mode;

		public readonly FriendArea Area;

		public bool IsJoinable
		{
			get
			{
				if (Mode == FriendGameMode.Joinable)
				{
					return LobbyId != 0;
				}
				return false;
			}
		}

		public bool IsInvitable => Mode == FriendGameMode.MainMenu;

		public FriendRoomInfo(ulong steamId, string name, ulong lobbyId, FriendGameMode mode)
			: this(steamId, name, lobbyId, mode, FriendArea.None)
		{
		}

		public FriendRoomInfo(ulong steamId, string name, ulong lobbyId, FriendGameMode mode, FriendArea area)
		{
			SteamId = steamId;
			Name = name ?? "";
			LobbyId = lobbyId;
			Mode = mode;
			Area = area;
		}
	}
	internal static class FriendRoomsService
	{
		public const uint PeakAppId = 3527290u;

		private const float RefreshIntervalSec = 10f;

		private const float NotifyMinIntervalSec = 1.5f;

		private const float RpRequestMinIntervalSec = 30f;

		private const string DisplayTokenPrefix = "#Status_";

		private static Callback<PersonaStateChange_t>? _personaChanged;

		private static Callback<FriendRichPresenceUpdate_t>? _richPresenceUpdate;

		private static FriendNotifyPump? _pump;

		private static List<FriendRoomInfo>? _cache;

		private static string _signature = "";

		private static float _nextRefresh;

		private static float _lastNotify;

		private static bool _dirty;

		private static bool _notifyPending;

		private static bool _inited;

		private static bool _loggedTokenSeen;

		private static bool _loggedFallbackSeen;

		private static readonly Dictionary<ulong, float> _lastRpRequest = new Dictionary<ulong, float>();

		public static event Action? Changed;

		public static void Init()
		{
			if (!_inited)
			{
				try
				{
					_personaChanged = Callback<PersonaStateChange_t>.Create((DispatchDelegate<PersonaStateChange_t>)OnPersonaChanged);
					_richPresenceUpdate = Callback<FriendRichPresenceUpdate_t>.Create((DispatchDelegate<FriendRichPresenceUpdate_t>)OnFriendRichPresenceUpdate);
					_inited = true;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("FriendRoomsService init failed: " + ex.Message));
				}
				StartPump();
			}
		}

		public static void Shutdown()
		{
			try
			{
				_personaChanged?.Dispose();
			}
			catch
			{
			}
			_personaChanged = null;
			try
			{
				_richPresenceUpdate?.Dispose();
			}
			catch
			{
			}
			_richPresenceUpdate = null;
			if ((Object)(object)_pump != (Object)null)
			{
				try
				{
					Object.Destroy((Object)(object)_pump);
				}
				catch
				{
				}
				_pump = null;
			}
			_inited = false;
			_cache = null;
			_signature = "";
			_dirty = false;
			_notifyPending = false;
			_lastRpRequest.Clear();
		}

		public static IReadOnlyList<FriendRoomInfo> GetFriends()
		{
			RefreshIfDue(force: false);
			IReadOnlyList<FriendRoomInfo> cache = _cache;
			return cache ?? Array.Empty<FriendRoomInfo>();
		}

		internal static void Tick()
		{
			if (_notifyPending && !(Time.unscaledTime - _lastNotify < 1.5f))
			{
				NotifyChanged();
			}
		}

		private static void StartPump()
		{
			try
			{
				if (!((Object)(object)_pump != (Object)null) && !((Object)(object)Plugin.Instance == (Object)null))
				{
					GameObject gameObject = ((Component)Plugin.Instance).gameObject;
					_pump = gameObject.GetComponent<FriendNotifyPump>() ?? gameObject.AddComponent<FriendNotifyPump>();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("FriendRoomsService pump failed: " + ex.Message));
			}
		}

		private static void RefreshIfDue(bool force)
		{
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: 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_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			if (!_inited || !SteamGate.Ready())
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!force && !_dirty && _cache != null && unscaledTime < _nextRefresh)
			{
				return;
			}
			List<FriendRoomInfo> list = new List<FriendRoomInfo>();
			HashSet<ulong> hashSet = new HashSet<ulong>();
			StringBuilder stringBuilder = new StringBuilder();
			try
			{
				int friendCount = SteamFriends.GetFriendCount((EFriendFlags)4);
				FriendGameInfo_t val = default(FriendGameInfo_t);
				for (int i = 0; i < friendCount; i++)
				{
					CSteamID friendByIndex = SteamFriends.GetFriendByIndex(i, (EFriendFlags)4);
					if (friendByIndex == CSteamID.Nil || friendByIndex.m_SteamID == 0L || !SteamFriends.GetFriendGamePlayed(friendByIndex, ref val) || ((CGameID)(ref val.m_gameID)).AppID().m_AppId != 3527290)
					{
						continue;
					}
					int num = 0;
					try
					{
						num = SteamFriends.GetFriendRichPresenceKeyCount(friendByIndex);
					}
					catch
					{
					}
					if (num != 0 || (int)PersonaState(friendByIndex) != 0)
					{
						ulong num2 = 0uL;
						if (((CSteamID)(ref val.m_steamIDLobby)).IsValid() && val.m_steamIDLobby.m_SteamID != 0L)
						{
							num2 = val.m_steamIDLobby.m_SteamID;
						}
						string display = "";
						string group = "";
						string players = "";
						string maxPlayers = "";
						if (num > 0)
						{
							ReadPresence(friendByIndex, out display, out group, out players, out maxPlayers);
						}
						FriendArea area;
						FriendGameMode friendGameMode = Classify(num2, num, display, group, players, maxPlayers, out area);
						RequestRichPresence(friendByIndex, unscaledTime);
						string text = "";
						try
						{
							text = SteamFriends.GetFriendPersonaName(friendByIndex) ?? "";
						}
						catch
						{
						}
						hashSet.Add(friendByIndex.m_SteamID);
						list.Add(new FriendRoomInfo(friendByIndex.m_SteamID, text, num2, friendGameMode, area));
						stringBuilder.Append(friendByIndex.m_SteamID).Append(':').Append(num2)
							.Append(':')
							.Append(num)
							.Append(':')
							.Append((int)friendGameMode)
							.Append(':')
							.Append((int)area)
							.Append(':')
							.Append(text)
							.Append('\n');
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("FriendRoomsService refresh failed: " + ex.Message));
				return;
			}
			PruneRpRequests(hashSet);
			string text2 = stringBuilder.ToString();
			bool flag = _cache == null || !string.Equals(text2, _signature, StringComparison.Ordinal);
			_cache = list.OrderByDescending((FriendRoomInfo f) => f.LobbyId != 0).ThenBy<FriendRoomInfo, string>((FriendRoomInfo f) => f.Name, StringComparer.OrdinalIgnoreCase).ToList();
			_signature = text2;
			_nextRefresh = Time.unscaledTime + 10f;
			_dirty = false;
			if (flag)
			{
				NotifyChanged();
			}
		}

		private static FriendGameMode Classify(ulong lobbyId, int keyCount, string display, string group, string players, string maxPlayers, out FriendArea area)
		{
			area = FriendArea.None;
			if (lobbyId != 0L)
			{
				if (TryParseArea(display, out var area2))
				{
					area = area2;
				}
				return FriendGameMode.Joinable;
			}
			if (keyCount == 0)
			{
				return FriendGameMode.Unknown;
			}
			if (TryParseArea(display, out var area3))
			{
				area = area3;
				if (!_loggedTokenSeen)
				{
					_loggedTokenSeen = true;
					Plugin.Log.LogInfo((object)("Friend rich presence token is raw: display=[" + display + "]"));
				}
				switch (area3)
				{
				case FriendArea.MainMenu:
					return FriendGameMode.MainMenu;
				case FriendArea.Airport:
					return FriendGameMode.Airport;
				default:
					if (!string.IsNullOrEmpty(group))
					{
						return FriendGameMode.InviteOnly;
					}
					if (!IsOne(maxPlayers))
					{
						return FriendGameMode.InviteOnly;
					}
					return FriendGameMode.Solo;
				}
			}
			if (!_loggedFallbackSeen)
			{
				_loggedFallbackSeen = true;
				Plugin.Log.LogInfo((object)("Friend rich presence token unusable, falling back to keys: display=[" + display + "] " + $"group=[{group}] players=[{players}] max=[{maxPlayers}] keys={keyCount}"));
			}
			if (!string.IsNullOrEmpty(group))
			{
				return FriendGameMode.InviteOnly;
			}
			if (IsOne(maxPlayers))
			{
				return FriendGameMode.Solo;
			}
			if (IsOne(players))
			{
				return FriendGameMode.MainMenu;
			}
			if (!string.IsNullOrEmpty(maxPlayers))
			{
				return FriendGameMode.InviteOnly;
			}
			return FriendGameMode.MainMenu;
		}

		private static bool TryParseArea(string display, out FriendArea area)
		{
			area = FriendArea.None;
			if (string.IsNullOrEmpty(display) || !display.StartsWith("#Status_", StringComparison.Ordinal))
			{
				return false;
			}
			switch (display.Substring("#Status_".Length))
			{
			case "MainMenu":
				area = FriendArea.MainMenu;
				return true;
			case "Airport":
				area = FriendArea.Airport;
				return true;
			case "Shore":
				area = FriendArea.Shore;
				return true;
			case "Tropics":
				area = FriendArea.Tropics;
				return true;
			case "Alpine":
				area = FriendArea.Alpine;
				return true;
			case "Mesa":
				area = FriendArea.Mesa;
				return true;
			case "Caldera":
				area = FriendArea.Caldera;
				return true;
			case "Kiln":
				area = FriendArea.Kiln;
				return true;
			case "Peak":
				area = FriendArea.Peak;
				return true;
			case "Roots":
				area = FriendArea.Roots;
				return true;
			case "Gloom":
				area = FriendArea.Gloom;
				return true;
			case "Citadel":
				area = FriendArea.Citadel;
				return true;
			case "Nadir":
				area = FriendArea.Nadir;
				return true;
			default:
				return false;
			}
		}

		private static void ReadPresence(CSteamID sid, out string display, out string group, out string players, out string maxPlayers)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			display = "";
			group = "";
			players = "";
			maxPlayers = "";
			try
			{
				display = SteamFriends.GetFriendRichPresence(sid, "steam_display") ?? "";
				group = SteamFriends.GetFriendRichPresence(sid, "steam_player_group") ?? "";
				players = SteamFriends.GetFriendRichPresence(sid, "players") ?? "";
				maxPlayers = SteamFriends.GetFriendRichPresence(sid, "max_players") ?? "";
			}
			catch
			{
			}
		}

		private static void RequestRichPresence(CSteamID sid, float now)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!_lastRpRequest.TryGetValue(sid.m_SteamID, out var value) || !(now - value < 30f))
				{
					_lastRpRequest[sid.m_SteamID] = now;
					SteamFriends.RequestFriendRichPresence(sid);
				}
			}
			catch
			{
			}
		}

		private static void PruneRpRequests(HashSet<ulong> present)
		{
			if (_lastRpRequest.Count == 0)
			{
				return;
			}
			List<ulong> list = null;
			foreach (ulong key in _lastRpRequest.Keys)
			{
				if (!present.Contains(key))
				{
					(list ?? (list = new List<ulong>())).Add(key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (ulong item in list)
			{
				_lastRpRequest.Remove(item);
			}
		}

		private static EPersonaState PersonaState(CSteamID sid)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return SteamFriends.GetFriendPersonaState(sid);
			}
			catch
			{
				return (EPersonaState)1;
			}
		}

		private static bool IsOne(string value)
		{
			return string.Equals(value, "1", StringComparison.Ordinal);
		}

		private static void OnPersonaChanged(PersonaStateChange_t cb)
		{
			MarkDirty();
		}

		private static void OnFriendRichPresenceUpdate(FriendRichPresenceUpdate_t cb)
		{
			MarkDirty();
		}

		private static void MarkDirty()
		{
			_dirty = true;
			RefreshIfDue(force: false);
		}

		private static void NotifyChanged()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - _lastNotify < 1.5f)
			{
				_notifyPending = true;
				return;
			}
			_lastNotify = unscaledTime;
			_notifyPending = false;
			try
			{
				FriendRoomsService.Changed?.Invoke();
			}
			catch
			{
			}
		}
	}
	internal sealed class FriendNotifyPump : MonoBehaviour
	{
		private void Update()
		{
			FriendRoomsService.Tick();
		}
	}
	internal sealed class HistoryPageController : IRowHost
	{
		private enum RowKind
		{
			None,
			Rooms,
			Players
		}

		private const string PlayersViewId = "__players__";

		private const string SearchViewId = "__search__";

		private const float DoubleClickSeconds = 0.4f;

		private const float RailGap = 24f;

		private RoomHubPage? _page;

		private RectTransform? _card;

		private RectTransform? _body;

		private RectTransform? _mainColumn;

		private RectTransform? _listRoot;

		private RectTransform? _emptyRoot;

		private RectTransform? _rail;

		private RectTransform? _banner;

		private UiScrollView? _list;

		private UiScrollView? _railList;

		private TextMeshProUGUI? _titleText;

		private TextMeshProUGUI? _subtitleText;

		private TextMeshProUGUI? _hintText;

		private TextMeshProUGUI? _emptyTitle;

		private TextMeshProUGUI? _emptyHint;

		private TextMeshProUGUI? _bannerText;

		private TextMeshProUGUI? _railTitle;

		private TextMeshProUGUI? _railHint;

		private TextMeshProUGUI? _railEmpty;

		private UiChip? _statChip;

		private UiButton? _tabRooms;

		private UiButton? _tabPlayers;

		private UiButton? _searchButton;

		private UiButton? _clearButton;

		private UiButton? _deleteButton;

		private UiButton? _closeButton;

		private UiTextInput? _searchInput;

		private RowPool? _mainPool;

		private RowPool? _railPool;

		private List<RowView> _mainRows = new List<RowView>();

		private List<RowView> _railRows = new List<RowView>();

		private RowKind _mainKind;

		private RowContext _context;

		private int _playerTotal;

		private bool _deleteMode;

		private string? _viewingLobbyId;

		private string? _searchQuery;

		private string? _lastClickLobbyId;

		private float _lastClickTime;

		private GameObject? _lastSelection;

		private bool _openedViaMenuWindow;

		private static readonly MethodInfo? MenuWindowOpen = typeof(MenuWindow).GetMethod("Open", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly MethodInfo? MenuWindowClose = typeof(MenuWindow).GetMethod("Close", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		public bool HasLivePage
		{
			get
			{
				if ((Object)(object)_page == (Object)null)
				{
					return false;
				}
				Component page = (Component)(object)_page;
				return (Object)(object)page != (Object)null;
			}
		}

		public bool IsPageOpen
		{
			get
			{
				if (!HasLivePage || !_openedViaMenuWindow)
				{
					return false;
				}
				try
				{
					return ((Component)_page).gameObject.activeInHierarchy;
				}
				catch
				{
					return false;
				}
			}
		}

		public bool IsSearchFocused
		{
			get
			{
				if (_searchInput != null)
				{
					return _searchInput.IsFocused;
				}
				return false;
			}
		}

		public void Open()
		{
			if (IsPageOpen)
			{
				return;
			}
			_deleteMode = false;
			_viewingLobbyId = null;
			_searchQuery = null;
			_lastClickLobbyId = null;
			HistoryUiFont.Resolve();
			EnsurePage();
			if (!HasLivePage)
			{
				return;
			}
			if (_searchInput != null)
			{
				_searchInput.Text = "";
			}
			try
			{
				((PeakCustomPage)_page).BlocksPlayerInput = true;
				((PeakCustomPage)_page).ShowCursorWhileOpen = true;
				((PeakCustomPage)_page).CloseOnUICancel = false;
				((PeakCustomPage)_page).CloseOnPause = false;
				((PeakCustomPage)_page).SelectOnOpen = true;
				_page.Layout();
				RefreshAll();
				_page.SetInitialSelection(FirstInteractive());
				if (!OpenMenuWindow())
				{
					if (!HistoryUi.IsTitleScene)
					{
						Plugin.Log.LogError((object)"MenuWindow.Open unavailable in-game — aborting open.");
						Discard();
						return;
					}
					_page.SkipStartClosed();
					((MenuWindow)_page).Show();
					((MenuWindow)_page).SetInputActive(true);
					_openedViaMenuWindow = true;
					Plugin.Log.LogWarning((object)"MenuWindow.Open unavailable — using Show on title.");
				}
				else
				{
					_openedViaMenuWindow = true;
				}
				_page.ApplySorting(apply: true);
				FocusInitial();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"History page Open failed: {arg}");
				Discard();
			}
		}

		public void Hide()
		{
			SteamInviteService.ResetInvited();
			ReleaseFocus();
			if (!HasLivePage)
			{
				_openedViaMenuWindow = false;
				return;
			}
			try
			{
				_page.ApplySorting(apply: false);
			}
			catch
			{
			}
			try
			{
				CloseMenuWindow();
			}
			catch
			{
			}
			_openedViaMenuWindow = false;
			try
			{
				if (HasLivePage && ((Component)_page).gameObject.activeInHierarchy)
				{
					((MenuWindow)_page).Hide();
				}
			}
			catch
			{
			}
		}

		public void Discard()
		{
			SteamInviteService.ResetInvited();
			ReleaseFocus();
			Unsubscribe();
			try
			{
				if (HasLivePage)
				{
					CloseMenuWindow();
				}
			}
			catch
			{
			}
			try
			{
				if (HasLivePage)
				{
					Object.Destroy((Object)(object)((Component)_page).gameObject);
				}
			}
			catch
			{
			}
			_mainPool?.Clear();
			_railPool?.Clear();
			_mainRows = new List<RowView>();
			_railRows = new List<RowView>();
			_mainKind = RowKind.None;
			_lastSelection = null;
			_page = null;
			_card = null;
			_body = null;
			_mainColumn = null;
			_listRoot = null;
			_emptyRoot = null;
			_rail = null;
			_banner = null;
			_list = null;
			_railList = null;
			_titleText = null;
			_subtitleText = null;
			_hintText = null;
			_emptyTitle = null;
			_emptyHint = null;
			_bannerText = null;
			_railTitle = null;
			_railEmpty = null;
			_statChip = null;
			_tabRooms = null;
			_tabPlayers = null;
			_searchButton = null;
			_clearButton = null;
			_deleteButton = null;
			_closeButton = null;
			_searchInput = null;
			_mainPool = null;
			_railPool = null;
			_deleteMode = false;
			_viewingLobbyId = null;
			_searchQuery = null;
			_openedViaMenuWindow = false;
		}

		public void HandleBackOrClose()
		{
			if (IsPageOpen)
			{
				if (_searchInput != null && _searchInput.IsFocused)
				{
					_searchInput.Deactivate();
					FocusInitial();
				}
				if (_viewingLobbyId != null)
				{
					ShowRoomsView();
				}
				else if (_deleteMode)
				{
					_deleteMode = false;
					RefreshAll();
				}
				else
				{
					Hide();
				}
			}
		}

		public void TickAvatarRefresh()
		{
			if (!IsPageOpen || Time.frameCount % 45 != 0)
			{
				return;
			}
			foreach (RowView item in _mainPool?.Live ?? Enumerable.Empty<RowView>())
			{
				item.RefreshAvatar();
			}
			foreach (RowView item2 in _railPool?.Live ?? Enumerable.Empty<RowView>())
			{
				item2.RefreshAvatar();
			}
		}

		public void TickSelection()
		{
			if (!IsPageOpen)
			{
				return;
			}
			EventSystem current = EventSystem.current;
			GameObject val = (((Object)(object)current == (Object)null) ? null : current.currentSelectedGameObject);
			if ((Object)(object)val == (Object)(object)_lastSelection)
			{
				return;
			}
			_lastSelection = val;
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Transform transform = val.transform;
			RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null);
			if (!((Object)(object)val2 == (Object)null))
			{
				if (IsInside((Transform?)(object)val2, _list?.Content))
				{
					_list.EnsureVisible(val2);
				}
				else if (IsInside((Transform?)(object)val2, _railList?.Content))
				{
					_railList.EnsureVisible(val2);
				}
			}
		}

		private static bool IsInside(Transform? child, RectTransform? container)
		{
			if ((Object)(object)child == (Object)null || (Object)(object)container == (Object)null)
			{
				return false;
			}
			Transform val = child;
			while ((Object)(object)val != (Object)null)
			{
				if ((Object)(object)val == (Object)(object)container)
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		private void EnsurePage()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if (!HasLivePage)
			{
				if ((Object)(object)_page != (Object)null)
				{
					Discard();
				}
				_page = new GameObject("Room Hub").AddComponent<RoomHubPage>();
				((PeakCustomPage)_page).CloseOnUICancel = false;
				((PeakCustomPage)_page).CloseOnPause = false;
				((PeakCustomPage)_page).BlocksPlayerInput = true;
				((PeakCustomPage)_page).ShowCursorWhileOpen = true;
				((PeakCustomPage)_page).SelectOnOpen = true;
				Image val = UiKit.Panel(((Component)_page).transform, "Scrim", UiTheme.Scrim, 0f);
				UiKit.Stretch(((Graphic)val).rectTransform);
				((Graphic)val).raycastTarget = true;
				BuildCard();
				BuildHeader();
				BuildTabs();
				BuildBody();
				BuildFooter();
				_page.Layout();
				_page.Resized += OnPageResized;
				JoinGate.StateChanged += OnJoinGateChanged;
				FriendRoomsService.Changed += OnFriendsChanged;
				LocalizationSync.Register(OnGameLanguageChanged);
			}
		}

		private void Unsubscribe()
		{
			if ((Object)(object)_page != (Object)null)
			{
				_page.Resized -= OnPageResized;
			}
			JoinGate.StateChanged -= OnJoinGateChanged;
			FriendRoomsService.Changed -= OnFriendsChanged;
			LocalizationSync.Unregister(OnGameLanguageChanged);
		}

		private void BuildCard()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			_card = UiKit.Card(((Component)_page).transform, "Card");
			UiKit.Anchor(_card, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(1440f, 840f));
			_page.SetCard(_card);
		}

		private void BuildHeader()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0271: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = UiKit.Rect((Transform)(object)_card, "Header");
			AnchorTop(val, 114f, 0f);
			float num = 12f;
			RectTransform val2 = UiKit.ClipBox((Transform)(object)val, "TitleBox");
			val2.anchorMin = new Vector2(0f, 1f);
			val2.anchorMax = new Vector2(1f, 1f);
			val2.pivot = new Vector2(0.5f, 1f);
			val2.offsetMin = new Vector2(32f, 0f - num - 64f);
			val2.offsetMax = new Vector2(-420f, 0f - num);
			_titleText = UiKit.Label((Transform)(object)val2, "Title", Loc.T("ROOMHISTORY_PAGE_TITLE"), 36f, UiTheme.Paper, (FontStyles)16);
			UiKit.Stretch(((TMP_Text)_titleText).rectTransform);
			float num2 = num + 64f;
			RectTransform val3 = UiKit.ClipBox((Transform)(object)val, "SubtitleBox");
			val3.anchorMin = new Vector2(0f, 1f);
			val3.anchorMax = new Vector2(1f, 1f);
			val3.pivot = new Vector2(0.5f, 1f);
			val3.offsetMin = new Vector2(32f, 0f - num2 - 32f);
			val3.offsetMax = new Vector2(-420f, 0f - num2);
			_subtitleText = UiKit.Label((Transform)(object)val3, "Subtitle", Loc.T("ROOMHISTORY_SUBTITLE"), 18f, UiTheme.TextDim, (FontStyles)0);
			UiKit.Stretch(((TMP_Text)_subtitleText).rectTransform);
			_statChip = UiChip.Create((Transform)(object)val, "Stat", "", ChipKind.Mute);
			UiKit.Anchor(_statChip.Rect, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-32f, 0f), new Vector2(0f, 34f));
			Image val4 = UiKit.Divider((Transform)(object)_card, "Rule");
			RectTransform rectTransform = ((Graphic)val4).rectTransform;
			rectTransform.anchorMin = new Vector2(0f, 1f);
			rectTransform.anchorMax = new Vector2(1f, 1f);
			rectTransform.pivot = new Vector2(0.5f, 1f);
			rectTransform.sizeDelta = new Vector2(-64f, 3f);
			rectTransform.anchoredPosition = new Vector2(0f, -114f);
		}

		private void BuildTabs()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_007f: 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: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Expected O, but got Unknown
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = UiKit.Rect((Transform)(object)_card, "Tabs");
			AnchorTop(val, 92f, 117f);
			_tabRooms = UiButton.Create((Transform)(object)val, "TabRooms", Loc.T("ROOMHISTORY_TAB_ROOMS"), UiRole.Primary);
			UiKit.Anchor(_tabRooms.Rect, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(32f, -16f), new Vector2(180f, 67f));
			((UnityEvent)_tabRooms.Control.onClick).AddListener(new UnityAction(ShowRoomsView));
			_tabPlayers = UiButton.Create((Transform)(object)val, "TabPlayers", Loc.T("ROOMHISTORY_TAB_PLAYERS"), UiRole.Ghost);
			UiKit.Anchor(_tabPlayers.Rect, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(224f, -16f), new Vector2(180f, 67f));
			((UnityEvent)_tabPlayers.Control.onClick).AddListener(new UnityAction(ShowAllPlayersView));
			_searchButton = UiButton.Create((Transform)(object)val, "SearchButton", Loc.T("ROOMHISTORY_SEARCH"), UiRole.Ghost);
			UiKit.Anchor(_searchButton.Rect, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-32f, -16f), new Vector2(150f, 67f));
			((UnityEvent)_searchButton.Control.onClick).AddListener(new UnityAction(RunSearch));
			_clearButton = UiButton.Create((Transform)(object)val, "ClearButton", Loc.T("ROOMHISTORY_SEARCH_CLEAR"), UiRole.Ghost);
			_clearButton.SetVisible(visible: false);
			((UnityEvent)_clearButton.Control.onClick).AddListener(new UnityAction(ShowRoomsView));
			_searchInput = UiTextInput.Create((Transform)(object)val, "SearchField", 320f, 67f, RunSearch);
			UiKit.Anchor(_searchInput.Rect, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-32f, -16f), new Vector2(320f, 67f));
			_searchInput.SetPlaceholder(Loc.T("ROOMHISTORY_SEARCH_PLACEHOLDER"));
		}

		private void BuildBody()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_0256: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_032a: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_037e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0399: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0414: Unknown result type (might be due to invalid IL or missing references)
			//IL_042a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0440: Unknown result type (might be due to invalid IL or missing references)
			//IL_045c: Unknown result type (might be due to invalid IL or missing references)
			_body = UiKit.Rect((Transform)(object)_card, "Body");
			_body.anchorMin = Vector2.zero;
			_body.anchorMax = Vector2.one;
			_body.offsetMin = new Vector2(32f, 96f);
			_body.offsetMax = new Vector2(-32f, -209f);
			_mainColumn = UiKit.Rect((Transform)(object)_body, "Main");
			_mainColumn.anchorMin = Vector2.zero;
			_mainColumn.anchorMax = Vector2.one;
			_mainColumn.offsetMin = Vector2.zero;
			_mainColumn.offsetMax = new Vector2(-524f, 0f);
			_banner = UiKit.Rect((Transform)(object)_mainColumn, "Banner");
			Image val = UiKit.Panel((Transform)(object)_banner, "Line", UiTheme.BannerBorder, 10f);
			UiKit.Stretch(((Graphic)val).rectTransform);
			Image val2 = UiKit.Panel((Transform)(object)_banner, "Fill", UiTheme.BannerBg, 9f);
			UiKit.Stretch(((Graphic)val2).rectTransform);
			((Graphic)val2).rectTransform.offsetMin = new Vector2(2f, 2f);
			((Graphic)val2).rectTransform.offsetMax = new Vector2(-2f, -2f);
			RectTransform val3 = UiKit.ClipBox((Transform)(object)_banner, "TextBox");
			UiKit.Stretch(val3);
			val3.offsetMin = new Vector2(20f, 0f);
			val3.offsetMax = new Vector2(-20f, 0f);
			_bannerText = UiKit.Label((Transform)(object)val3, "Text", Loc.T("ROOMHISTORY_DELETE_BANNER"), 18f, UiTheme.BannerText, (FontStyles)0);
			UiKit.Stretch(((TMP_Text)_bannerText).rectTransform);
			((TMP_Text)_bannerText).alignment = (TextAlignmentOptions)4097;
			_banner.anchorMin = new Vector2(0f, 1f);
			_banner.anchorMax = new Vector2(1f, 1f);
			_banner.pivot = new Vector2(0.5f, 1f);
			_banner.sizeDelta = new Vector2(0f, 60f);
			_banner.anchoredPosition = Vector2.zero;
			((Component)_banner).gameObject.SetActive(false);
			_listRoot = UiKit.Rect((Transform)(object)_mainColumn, "ListRoot");
			_listRoot.anchorMin = Vector2.zero;
			_listRoot.anchorMax = Vector2.one;
			_listRoot.offsetMin = Vector2.zero;
			_listRoot.offsetMax = Vector2.zero;
			_list = UiScrollView.Create((Transform)(object)_listRoot, "RoomHubList");
			UiKit.Stretch(_list.Root);
			_emptyRoot = UiKit.Rect((Transform)(object)_mainColumn, "Empty");
			UiKit.Stretch(_emptyRoot);
			RectTransform val4 = UiKit.ClipBox((Transform)(object)_emptyRoot, "TitleBox");
			val4.anchorMin = new Vector2(0f, 0.5f);
			val4.anchorMax = new Vector2(1f, 0.5f);
			val4.pivot = new Vector2(0.5f, 0.5f);
			val4.sizeDelta = new Vector2(0f, 42f);
			val4.anchoredPosition = new Vector2(0f, 26f);
			_emptyTitle = UiKit.Label((Transform)(object)val4, "Title", "", 24f, UiTheme.Paper, (FontStyles)0, wrap: true);
			UiKit.Stretch(((TMP_Text)_emptyTitle).rectTransform);
			((TMP_Text)_emptyTitle).alignment = (TextAlignmentOptions)514;
			RectTransform val5 = UiKit.ClipBox((Transform)(object)_emptyRoot, "HintBox");
			val5.anchorMin = new Vector2(0f, 0.5f);
			val5.anchorMax = new Vector2(1f, 0.5f);
			val5.pivot = new Vector2(0.5f, 0.5f);
			val5.sizeDelta = new Vector2(0f, 32f);
			val5.anchoredPosition = new Vector2(0f, -28f);
			_emptyHint = UiKit.Label((Transform)(object)val5, "Hint", "", 18f, UiTheme.TextFaint, (FontStyles)0, wrap: true);
			UiKit.Stretch(((TMP_Text)_emptyHint).rectTransform);
			((TMP_Text)_emptyHint).alignment = (TextAlignmentOptions)514;
			((Component)_emptyRoot).gameObject.SetActive(false);
			BuildRail();
			_mainPool = new RowPool(CreateMainRow);
			_railPool = new RowPool((string key) => FriendRow.Create((Transform)(object)_railList.Content, key, this));
		}

		private void BuildRail()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: 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_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			//IL_032f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0345: Unknown result type (might be due to invalid IL or missing references)
			//IL_035b: Unknown result type (might be due to invalid IL or missing references)
			//IL_037c: Unknown result type (might be due to invalid IL or missing references)
			_rail = UiKit.Rect((Transform)(object)_body, "Rail");
			_rail.anchorMin = new Vector2(1f, 0f);
			_rail.anchorMax = new Vector2(1f, 1f);
			_rail.pivot = new Vector2(1f, 0.5f);
			_rail.sizeDelta = new Vector2(500f, 0f);
			_rail.anchoredPosition = Vector2.zero;
			Image val = UiKit.Divider((Transform)(object)_rail, "Line");
			RectTransform rectTransform = ((Graphic)val).rectTransform;
			rectTransform.anchorMin = new Vector2(0f, 0f);
			rectTransform.anchorMax = new Vector2(0f, 1f);
			rectTransform.pivot = new Vector2(0f, 0.5f);
			rectTransform.sizeDelta = new Vector2(3f, 0f);
			rectTransform.anchoredPosition = Vector2.zero;
			((Graphic)val).color = new Color(UiTheme.Divider.r, UiTheme.Divider.g, UiTheme.Divider.b, 0.5f);
			_railTitle = UiKit.Label((Transform)(object)_rail, "Title", "", 22f, UiTheme.Gold, (FontStyles)16);
			RectTransform rectTransform2 = ((TMP_Text)_railTitle).rectTransform;
			rectTransform2.anchorMin = new Vector2(0f, 1f);
			rectTransform2.anchorMax = new Vector2(1f, 1f);
			rectTransform2.pivot = new Vector2(0.5f, 1f);
			rectTransform2.offsetMin = new Vector2(22f, -40f);
			rectTransform2.offsetMax = new Vector2(0f, 0f);
			_railHint = UiKit.Label((Transform)(object)_rail, "Hint", Loc.T("ROOMHISTORY_RAIL_HINT"), 16f, UiTheme.TextFaint, (FontStyles)0);
			RectTransform rectTransform3 = ((TMP_Text)_railHint).rectTransform;
			rectTransform3.anchorMin = new Vector2(0f, 1f);
			rectTransform3.anchorMax = new Vector2(1f, 1f);
			rectTransform3.pivot = new Vector2(0.5f, 1f);
			rectTransform3.offsetMin = new Vector2(22f, -68f);
			rectTransform3.offsetMax = new Vector2(0f, -40f);
			RectTransform val2 = UiKit.Rect((Transform)(object)_rail, "ListRoot");
			val2.anchorMin = Vector2.zero;
			val2.anchorMax = Vector2.one;
			val2.offsetMin = new Vector2(22f, 0f);
			val2.offsetMax = new Vector2(0f, -74f);
			_railList = UiScrollView.Create((Transform)(object)val2, "RoomHubFriendList");
			UiKit.Stretch(_railList.Root);
			RectTransform val3 = UiKit.ClipBox((Transform)(object)val2, "EmptyBox");
			val3.anchorMin = new Vector2(0f, 1f);
			val3.anchorMax = new Vector2(1f, 1f);
			val3.pivot = new Vector2(0.5f, 1f);
			val3.sizeDelta = new Vector2(0f, 64f);
			val3.anchoredPosition = new Vector2(0f, -12f);
			_railEmpty = UiKit.Label((Transform)(object)val3, "Empty", Loc.T("ROOMHISTORY_FRIENDS_EMPTY"), 18f, UiTheme.TextFaint, (FontStyles)0, wrap: true);
			UiKit.Stretch(((TMP_Text)_railEmpty).rectTransform);
		}

		private void BuildFooter()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Expected O, but got Unknown
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Expected O, but got Unknown
			RectTransform val = UiKit.Rect((Transform)(object)_card, "Footer");
			val.anchorMin = new Vector2(0f, 0f);
			val.anchorMax = new Vector2(1f, 0f);
			val.pivot = new Vector2(0.5f, 0f);
			val.sizeDelta = new Vector2(0f, 96f);
			val.anchoredPosition = Vector2.zero;
			RectTransform val2 = UiKit.ClipBox((Transform)(object)val, "HintBox");
			val2.anchorMin = new Vector2(0f, 0.5f);
			val2.anchorMax = new Vector2(1f, 0.5f);
			val2.pivot = new Vector2(0.5f, 0.5f);
			val2.offsetMin = new Vector2(32f, -14f);
			val2.offsetMax = new Vector2(-372f, 14f);
			_hintText = UiKit.Label((Transform)(object)val2, "Hint", "", 16f, UiTheme.TextFaint, (FontStyles)0);
			UiKit.Stretch(((TMP_Text)_hintText).rectTransform);
			_closeButton = UiButton.Create((Transform)(object)val, "Close", Loc.T("ROOMHISTORY_CLOSE"), UiRole.Primary);
			UiKit.Anchor(_closeButton.Rect, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-32f, 0f), new Vector2(150f, 67f));
			((UnityEvent)_closeButton.Control.onClick).AddListener((UnityAction)delegate
			{
				_deleteMode = false;
				Hide();
			});
			_deleteButton = UiButton.Create((Transform)(object)val, "DeleteMode", Loc.T("ROOMHISTORY_DELETE_MODE_ON"), UiRole.Ghost);
			UiKit.Anchor(_deleteButton.Rect, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), Vector2.zero, new Vector2(150f, 67f));
			((UnityEvent)_deleteButton.Control.onClick).AddListener(new UnityAction(ToggleDeleteMode));
		}

		private RowView CreateMainRow(string key)
		{
			RectTransform content = _list.Content;
			if (_mainKind != RowKind.Players)
			{
				return RoomRow.Create((Transform)(object)content, key, this);
			}
			return PlayerRow.Create((Transform)(object)content, key, this);
		}

		private RowKind CurrentKind()
		{
			if (_viewingLobbyId == null)
			{
				return RowKind.Rooms;
			}
			return RowKind.Players;
		}

		private void RefreshAll()
		{
			if (!((Object)(object)_page == (Object)null) && _list != null)
			{
				_context = new RowContext(HistoryUi.IsTitleScene || !HistoryUi.IsInLiveLobby, HistoryUi.CanShowInvite);
				IReadOnlyList<RoomRecord> rooms = Plugin.Store.GetRooms();
				IReadOnlyList<FriendRoomInfo> friends = FriendRoomsService.GetFriends();
				_playerTotal = AllMetPlayers().Count;
				RefreshChrome(rooms.Count, friends.Count);
				RefreshMainList(rooms);
				RefreshRail(friends);
				WireNavigation();
				RefreshInitialSelection();
			}
		}

		private void RefreshChrome(int roomCount, int friendCount)
		{
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0378: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_043c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0478: Unknown result type (might be due to invalid IL or missing references)
			//IL_059a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0688: Unknown result type (might be due to invalid IL or missing references)
			//IL_0616: Unknown result type (might be due to invalid IL or missing references)
			//IL_0507: Unknown result type (might be due to invalid IL or missing references)
			bool flag = _viewingLobbyId == "__search__";
			bool flag2 = _viewingLobbyId != null && !flag;
			bool flag3 = _viewingLobbyId == null;
			TextMeshProUGUI? titleText = _titleText;
			if (titleText != null)
			{
				((TMP_Text)titleText).SetText(Loc.T("ROOMHISTORY_PAGE_TITLE"));
			}
			if ((Object)(object)_subtitleText != (Object)null)
			{
				if (flag)
				{
					((TMP_Text)_subtitleText).text = Loc.Tf("ROOMHISTORY_SEARCH_QUERY", _searchQuery ?? "");
				}
				else if (_viewingLobbyId == "__players__")
				{
					((TMP_Text)_subtitleText).text = Loc.T("ROOMHISTORY_PLAYERS_TITLE");
				}
				else if (flag2)
				{
					RoomRecord room = Plugin.Store.GetRoom(_viewingLobbyId);
					string text = ((room == null || string.IsNullOrWhiteSpace(room.HostName)) ? Loc.T("ROOMHISTORY_UNKNOWN_HOST") : room.HostName);
					((TMP_Text)_subtitleText).text = Loc.Tf("ROOMHISTORY_ROOM_TITLE", text) + "  ·  " + Loc.Tf("ROOMHISTORY_PLAYER_COUNT", room?.Players.Count ?? 0);
				}
				else
				{
					((TMP_Text)_subtitleText).text = Loc.T("ROOMHISTORY_SUBTITLE");
				}
			}
			_statChip?.SetText(Loc.Tf("ROOMHISTORY_STAT_PLAYERS", _playerTotal), ChipKind.Mute);
			if (_tabRooms != null)
			{
				_tabRooms.SetMinWidth(180f);
				_tabRooms.SetLabel(Loc.Tf("ROOMHISTORY_TAB_ROOMS_COUNT", roomCount));
				_tabRooms.SetTabActive(flag3);
			}
			if (_tabPlayers != null)
			{
				_tabPlayers.SetMinWidth(180f);
				_tabPlayers.SetLabel(Loc.T("ROOMHISTORY_TAB_PLAYERS"));
				_tabPlayers.Rect.anchoredPosition = new Vector2(32f + _tabRooms.Width + 12f, -16f);
				_tabPlayers.SetTabActive(!flag3);
			}
			float num = 556f;
			float num2;
			if (!((Object)(object)_card != (Object)null))
			{
				num2 = 1440f;
			}
			else
			{
				Rect rect = _card.rect;
				num2 = ((Rect)(ref rect)).width;
			}
			float num3 = num2;
			float num4 = num3 - 64f - 500f - 24f;
			float num5 = (_tabRooms?.Width ?? 0f) + 12f + (_tabPlayers?.Width ?? 0f);
			float num6 = _searchButton?.Width ?? 0f;
			float num7 = ((flag && _clearButton != null) ? (_clearButton.Width + 10f) : 0f);
			float num8 = num4 - num5 - 24f - num6 - 10f;
			bool flag4 = num8 - num7 >= 120f;
			float num9 = Mathf.Clamp(num8 - (flag4 ? num7 : 0f), 120f, 320f);
			if (_searchButton != null)
			{
				_searchButton.SetLabel(Loc.T("ROOMHISTORY_SEARCH"));
				_searchButton.Rect.anchoredPosition = new Vector2(0f - num, -17f);
			}
			if (_searchInput != null)
			{
				_searchInput.SetVisible(visible: true);
				_searchInput.SetPlaceholder(Loc.T("ROOMHISTORY_SEARCH_PLACEHOLDER"));
				_searchInput.Rect.sizeDelta = new Vector2(num9, 67f);
				_searchInput.Rect.anchoredPosition = new Vector2(0f - (num + num6 + 10f), -16f);
			}
			if (_clearButton != null)
			{
				_clearButton.SetLabel(Loc.T("ROOMHISTORY_SEARCH_CLEAR"));
				_clearButton.SetVisible(flag && flag4);
				_clearButton.Rect.anchoredPosition = new Vector2(0f - (num + num6 + 10f + num9 + 10f), -16f);
			}
			if (_closeButton != null)
			{
				_closeButton.SetLabel(Loc.T("ROOMHISTORY_CLOSE"));
				_closeButton.Rect.anchoredPosition = new Vector2(-32f, 0f);
			}
			if (_deleteButton != null)
			{
				_deleteButton.SetLabel(Loc.T(_deleteMode ? "ROOMHISTORY_DELETE_MODE_OFF" : "ROOMHISTORY_DELETE_MODE_ON"));
				_deleteButton.SetRole(_deleteMode ? UiRole.Danger : UiRole.Ghost);
				_deleteButton.SetVisible(flag3);
				_deleteButton.Rect.anchoredPosition = new Vector2(0f - (32f + (_closeButton?.Width ?? 0f) + 12f), 0f);
			}
			if ((Object)(object)_hintText != (Object)null)
			{
				if (_deleteMode)
				{
					((TMP_Text)_hintText).text = Loc.T("ROOMHISTORY_HINT_ESC_DELETE") + "  ·  " + Loc.T("ROOMHISTORY_DELETE_CONFIRM");
				}
				else
				{
					string text2 = Loc.T("ROOMHISTORY_HINT_ESC");
					if (flag3)
					{
						text2 = text2 + "  ·  " + Loc.T("ROOMHISTORY_HINT_DOUBLECLICK");
					}
					text2 = text2 + "  ·  " + Loc.Tf("ROOMHISTORY_HINT_HOTKEY_SHORT", Plugin.ModConfig.ModMenuHotkey.Value);
					((TMP_Text)_hintText).text = text2;
				}
				float num10 = 32f + (_closeButton?.Width ?? 0f) + 24f;
				if (flag3 && _deleteButton != null)
				{
					num10 += _deleteButton.Width + 12f;
				}
				((TMP_Text)_hintText).rectTransform.offsetMax = new Vector2(0f - num10, 14f);
			}
			bool flag5 = _deleteMode && flag3;
			if ((Object)(object)_banner != (Object)null)
			{
				((Component)_banner).gameObject.SetActive(flag5);
				if ((Object)(object)_bannerText != (Object)null)
				{
					((TMP_Text)_bannerText).text = Loc.T("ROOMHISTORY_DELETE_BANNER");
				}
				_listRoot.offsetMax = new Vector2(0f, flag5 ? (-70f) : 0f);
			}
			if ((Object)(object)_railTitle != (Object)null)
			{
				((TMP_Text)_railTitle).text = Loc.Tf("ROOMHISTORY_RAIL_TITLE", friendCount);
			}
			if ((Object)(object)_railHint != (Object)null)
			{
				((TMP_Text)_railHint).text = Loc.T("ROOMHISTORY_RAIL_HINT");
			}
		}

		private void RefreshMainList(IReadOnlyList<RoomRecord>? rooms = null)
		{
			if (_mainPool == null || _list == null)
			{
				return;
			}
			RowKind rowKind = CurrentKind();
			if (rowKind != _mainKind)
			{
				_mainPool.Clear();
				_mainKind = rowKind;
			}
			List<RowView> list = new List<RowView>();
			switch (rowKind)
			{
			case RowKind.Rooms:
				if (rooms == null)
				{
					rooms = Plugin.Store.GetRooms();
				}
				foreach (RoomRecord room in rooms)
				{
					RoomRow roomRow = (RoomRow)_mainPool.Take(room.LobbyId);
					roomRow.Bind(room, _deleteMode, _context);
					list.Add(roomRow);
				}
				break;
			case RowKind.Players:
				foreach (PlayerSearchHit item in CurrentPlayerHits())
				{
					PlayerRow playerRow = (PlayerRow)_mainPool.Take(item.SteamId);
					playerRow.Bind(item, _context);
					list.Add(playerRow);
				}
				break;
			}
			_mainRows = _mainPool.Commit(list);
			_list.LayoutRows(Rects(_mainRows), 96f, 10f);
			bool flag = _mainRows.Count == 0;
			((Component)_list.Root).gameObject.SetActive(!flag);
			if ((Object)(object)_emptyRoot != (Object)null)
			{
				((Component)_emptyRoot).gameObject.SetActive(flag);
			}
			if (flag)
			{
				SetEmptyState(rowKind);
			}
		}

		private void RefreshRail(IReadOnlyList<FriendRoomInfo>? friends = null)
		{
			if (_railPool == null || _railList == null)
			{
				return;
			}
			if (friends == null)
			{
				friends = FriendRoomsService.GetFriends();
			}
			List<RowView> list = new List<RowView>();
			foreach (FriendRoomInfo friend in friends)
			{
				FriendRow friendRow = (FriendRow)_railPool.Take(friend.SteamId.ToString());
				friendRow.Bind(friend, _context);
				list.Add(friendRow);
			}
			_railRows = _railPool.Commit(list);
			_railList.LayoutRows(Rects(_railRows), 96f, 10f);
			((Component)_railList.Root).gameObject.SetActive(_railRows.Count > 0);
			if ((Object)(object)_railEmpty != (Object)null)
			{
				((TMP_Text)_railEmpty).text = Loc.T("ROOMHISTORY_FRIENDS_EMPTY");
				((Component)_railEmpty).gameObject.SetActive(_railRows.Count == 0);
			}
		}

		private List<PlayerSearchHit> CurrentPlayerHits()
		{
			if (_viewingLobbyId == "__search__")
			{
				return Plugin.Store.SearchPlayers(_searchQuery ?? "").ToList();
			}
			if (_viewingLobbyId == "__players__")
			{
				return AllMetPlayers();
			}
			RoomRecord room = ((_viewingLobbyId == null) ? null : Plugin.Store.GetRoom(_viewingLobbyId));
			if (room == null)
			{
				return new List<PlayerSearchHit>();
			}
			return (from p in room.Players
				orderby p.WasHost descending, p.LastSeenUtcTicks descending
				select new PlayerSearchHit
				{
					SteamId = p.SteamId,
					DisplayName = p.DisplayName,
					WasHost = p.WasHost,
					LastSeenUtcTicks = p.LastSeenUtcTicks,
					LobbyId = room.LobbyId,
					RoomHostName = room.HostName
				}).ToList();
		}

		private List<PlayerSearchHit> AllMetPlayers()
		{
			Dictionary<string, PlayerSearchHit> dictionary = new Dictionary<string, PlayerSearchHit>(StringComparer.Ordinal);
			foreach (RoomRecord room in Plugin.Store.GetRooms())
			{
				foreach (PlayerRecord player in room.Players)
				{
					if (!string.IsNullOrWhiteSpace(player.SteamId) && (!dictionary.TryGetValue(player.SteamId, out var value) || value.LastSeenUtcTicks < player.LastSeenUtcTicks))
					{
						dictionary[player.SteamId] = new PlayerSearchHit
						{
							SteamId = player.SteamId,
							DisplayName = player.DisplayName,
							WasHost = player.WasHost,
							LastSeenUtcTicks = player.LastSeenUtcTicks,
							LobbyId = room.LobbyId,
							RoomHostName = room.HostName
						};
					}
				}
			}
			return dictionary.Values.OrderByDescending((PlayerSearchHit h) => h.LastSeenUtcTicks).ToList();
		}

		private void SetEmptyState(RowKind kind)
		{
			if ((Object)(object)_emptyTitle == (Object)null || (Object)(object)_emptyHint == (Object)null)
			{
				return;
			}
			if (kind == RowKind.Players)
			{
				if (_viewingLobbyId == "__search__")
				{
					((TMP_Text)_emptyTitle).text = Loc.T("ROOMHISTORY_SEARCH_EMPTY");
					((TMP_Text)_emptyHint).text = Loc.T("ROOMHISTORY_SEARCH_EMPTY_HINT");
				}
				else if (_viewingLobbyId == "__players__")
				{
					((TMP_Text)_emptyTitle).text = Loc.T("ROOMHISTORY_EMPTY_ALL_PLAYERS");
					((TMP_Text)_emptyHint).text = Loc.T("ROOMHISTORY_EMPTY_PLAYERS_HINT");
				}
				else
				{
					((TMP_Text)_emptyTitle).text = Loc.T("ROOMHISTORY_EMPTY_PLAYERS");
					((TMP_Text)_emptyHint).text = Loc.T("ROOMHISTORY_EMPTY_PLAYERS_HINT");
				}
			}
			else
			{
				((TMP_Text)_emptyTitle).text = Loc.T("ROOMHISTORY_EMPTY_ROOMS_TITLE");
				((TMP_Text)_emptyHint).text = Loc.T("ROOMHISTORY_EMPTY_ROOMS_HINT");
			}
		}

		private void WireNavigation()
		{
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_046b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0470: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			List<Selectable> list = new List<Selectable>();
			if (_tabRooms != null)
			{
				list.Add((Selectable)(object)_tabRooms.Control);
			}
			if (_tabPlayers != null)
			{
				list.Add((Selectable)(object)_tabPlayers.Control);
			}
			if (_searchInput != null)
			{
				list.Add((Selectable)(object)_searchInput.Field);
			}
			if (_searchButton != null)
			{
				list.Add((Selectable)(object)_searchButton.Control);
			}
			if (_clearButton != null && _clearButton.GameObject.activeSelf)
			{
				list.Add((Selectable)(object)_clearButton.Control);
			}
			List<List<Selectable>> list2 = new List<List<Selectable>>();
			foreach (RowView mainRow in _mainRows)
			{
				List<Selectable> list3 = mainRow.ActionSelectables();
				if (list3.Count > 0)
				{
					list2.Add(list3);
				}
			}
			List<Selectable> list4 = new List<Selectable>();
			if (_deleteButton != null && _deleteButton.GameObject.activeSelf)
			{
				list4.Add((Selectable)(object)_deleteButton.Control);
			}
			if (_closeButton != null)
			{
				list4.Add((Selectable)(object)_closeButton.Control);
			}
			UiNav.Row(list);
			foreach (List<Selectable> item in list2)
			{
				UiNav.Row(item);
			}
			UiNav.Row(list4);
			for (int i = 0; i < list2.Count; i++)
			{
				Selectable up = ((i != 0) ? list2[i - 1][0] : ((list.Count > 0) ? list[0] : null));
				Selectable down = ((i != list2.Count - 1) ? list2[i + 1][0] : ((list4.Count > 0) ? list4[0] : null));
				foreach (Selectable item2 in list2[i])
				{
					Navigation navigation = item2.navigation;
					UiNav.Link(item2, up, down, ((Navigation)(ref navigation)).selectOnLeft, ((Navigation)(ref navigation)).selectOnRight);
				}
			}
			Selectable val = ((list2.Count > 0) ? list2[0][0] : null);
			Selectable val2 = ((list2.Count > 0) ? list2[list2.Count - 1][0] : null);
			foreach (Selectable item3 in list)
			{
				Navigation navigation2 = item3.navigation;
				Selectable down2 = val ?? ((list4.Count > 0) ? list4[0] : null);
				UiNav.Link(item3, null, down2, ((Navigation)(ref navigation2)).selectOnLeft, ((Navigation)(ref navigation2)).selectOnRight);
			}
			foreach (Selectable item4 in list4)
			{
				Navigation navigation3 = item4.navigation;
				Selectable up2 = val2 ?? ((list.Count > 0) ? list[0] : null);
				UiNav.Link(item4, up2, null, ((Navigation)(ref navigation3)).selectOnLeft, ((Navigation)(ref navigation3)).selectOnRight);
			}
			List<Selectable> list5 = new List<Selectable>();
			foreach (RowView railRow in _railRows)
			{
				Selectable val3 = railRow.FirstSelectable();
				if ((Object)(object)val3 != (Object)null)
				{
					list5.Add(val3);
				}
			}
			UiNav.Column(list5);
			for (int j = 0; j < list5.Count; j++)
			{
				List<Selectable> list6 = ((list2.Count == 0) ? null : list2[Math.Min(j, list2.Count - 1)]);
				Selectable left = list6?[list6.Count - 1];
				Navigation navigation4 = list5[j].navigation;
				UiNav.Link(list5[j], ((Navigation)(ref navigation4)).selectOnUp, ((Navigation)(ref navigation4)).selectOnDown, left, ((Navigation)(ref navigation4)).selectOnRight);
			}
			for (int k = 0; k < list2.Count; k++)
			{
				if (list5.Count <= 0)
				{
					break;
				}
				Selectable right = list5[Math.Min(k, list5.Count - 1)];
				Selectable val4 = list2[k][list2[k].Count - 1];
				Navigation navigation5 = val4.navigation;
				UiNav.Link(val4, ((Navigation)(ref navigation5)).selectOnUp, ((Navigation)(ref navigation5)).selectOnDown, ((Navigation)(ref navigation5)).selectOnLeft, right);
			}
		}

		private Selectable? FirstInteractive()
		{
			foreach (RowView mainRow in _mainRows)
			{
				Selectable val = mainRow.FirstSelectable();
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return (Selectable?)(object)_closeButton?.Control;
		}

		private void ReleaseFocus()
		{
			try
			{
				EventSystem current = EventSystem.current;
				if (!((Object)(object)current == (Object)null))
				{
					GameObject currentSelectedGameObject = current.currentSelectedGameObject;
					if (!((Object)(object)currentSelectedGameObject == (Object)null) && IsInside(currentSelectedGameObject.transform, _card))
					{
						current.SetSelectedGameObject((GameObject)null);
					}
				}
			}
			catch
			{
			}
		}

		private void FocusInitial()
		{
			if ((Object)(object)_page == (Object)null)
			{
				return;
			}
			Selectable val = FirstInteractive();
			_page.SetInitialSelection(val);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				UIInputHandler.SetSelectedObject(((Component)val).gameObject);
			}
			catch
			{
			}
		}

		private void RefreshInitialSelection()
		{
			if ((Object)(object)_page == (Object)null || !IsPageOpen)
			{
				return;
			}
			Selectable val = FirstInteractive();
			_page.SetInitialSelection(val);
			EventSystem current = EventSystem.current;
			if (((Object)(object)current != (Object)null && (Object)(object)current.currentSelectedGameObject != (Object)null && current.currentSelectedGameObject.activeInHierarchy) || (Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				UIInputHandler.SetSelectedObject(((Component)val).gameObject);
			}
			catch
			{
			}
		}

		private void OnPageResized()
		{
			if (!IsPageOpen)
			{
				return;
			}
			try
			{
				RefreshChrome(Plugin.Store.GetRooms().Count, FriendRoomsService.GetFriends().Count);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("OnPageResized: " + ex.Message));
			}
		}

		private void OnGameLanguageChanged()
		{
			try
			{
				if (IsPageOpen)
				{
					RefreshAll();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("OnGameLanguageChanged: " + ex.Message));
			}
		}

		private void OnJoinGateChanged()
		{
			if (!IsPageOpen)
			{
				return;
			}
			try
			{
				RefreshMainList();
				WireNavigation();
				RefreshInitialSelection();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("OnJoinGateChanged: " + ex.Message));
			}
		}

		private void OnFriendsChanged()
		{
			if (!IsPageOpen)
			{
				return;
			}
			try
			{
				IReadOnlyList<FriendRoomInfo> friends = FriendRoomsService.GetFriends();
				if ((Object)(object)_railTitle != (Object)null)
				{
					((TMP_Text)_railTitle).text = Loc.Tf("ROOMHISTORY_RAIL_TITLE", friends.Count);
				}
				_statChip?.SetText(Loc.Tf("ROOMHISTORY_STAT_PLAYERS", _playerTotal), ChipKind.Mute);
				RefreshRail(friends);
				WireNavigation();
				RefreshInitialSelection();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("OnFriendsChanged: " + ex.Message));
			}
		}

		private void ToggleDeleteMode()
		{
			if (_viewingLobbyId == null)
			{
				_deleteMode = !_deleteMode;
				RefreshAll();
			}
		}

		private void ShowRoomsView()
		{
			_viewingLobbyId = null;
			_searchQuery = null;
			_deleteMode = false;
			if (_searchInput != null)
			{
				_searchInput.Text = "";
			}
			RefreshAll();
			_list?.ResetScroll();
		}

		private void ShowAllPlayersView()
		{
			_viewingLobbyId = "__players__";
			_searchQuery = null;
			_deleteMode = false;
			RefreshAll();
			_list?.ResetScroll();
		}

		private void ShowPlayersView(string lobbyId)
		{
			_viewingLobbyId = lobbyId;
			_searchQuery = null;
			_deleteMode = false;
			RefreshAll();
			_list?.ResetScroll();
		}

		private void RunSearch()
		{
			string text = (_searchInput?.Text ?? "").Trim();
			if (text.Length == 0)
			{
				ShowAllPlayersView();
				return;
			}
			_searchQuery = text;
			_viewingLobbyId = "__search__";
			_deleteMode = false;
			RefreshAll();
			_list?.ResetScroll();
		}

		private static List<RectTransform> Rects(IReadOnlyList<RowView> rows)
		{
			List<RectTransform> list = new List<RectTransform>(rows.Count);
			foreach (RowView row in rows)
			{
				list.Add(row.Root);
			}
			return list;
		}

		private static void AnchorTop(RectTransform rect, float height, float topOffset)
		{
			//IL_000b: 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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			rect.anchorMin = new Vector2(0f, 1f);
			rect.anchorMax = new Vector2(1f, 1f);
			rect.pivot = new Vector2(0.5f, 1f);
			rect.sizeDelta = new Vector2(0f, height);
			rect.anchoredPosition = new Vector2(0f, 0f - topOffset);
		}

		private bool OpenMenuWindow()
		{
			if (!HasLivePage || MenuWindowOpen == null)
			{
				return false;
			}
			try
			{
				MenuWindowOpen.Invoke(_page, null);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("MenuWindow.Open invoke failed: " + ex.Message));
				return false;
			}
		}

		private void CloseMenuWindow()
		{
			if (!HasLivePage)
			{
				return;
			}
			try
			{
				if (MenuWindowClose != null)
				{
					MenuWindowClose.Invoke(_page, null);
				}
				else
				{
					((MenuWindow)_page).Hide();
				}
			}
			catch
			{
				try
				{
					((MenuWindow)_page).Hide();
				}
				catch
				{
				}
			}
		}

		public void OnRoomClicked(RoomRecord room)
		{
			float unscaledTime = Time.unscaledTime;
			bool flag = _lastClickLobbyId != null && string.Equals(_lastClickLobbyId, room.LobbyId, StringComparison.Ordinal) && unscaledTime - _lastClickTime <= 0.4f;
			_lastClickLobbyId = room.LobbyId;
			_lastClickTime = unscaledTime;
			if (flag)
			{
				if (!HistoryUi.IsTitleScene && HistoryUi.IsInLiveLobby)
				{
					Plugin.Log.LogDebug((object)"Double-click join ignored: already in a live lobby.");
					return;
				}
				_lastClickLobbyId = null;
				RoomJoiner.TryJoin(room);
			}
		}

		public void OnRoomJoin(RoomRecord room)
		{
			RoomJoiner.TryJoin(room);
		}

		public void OnRoomView(RoomRecord room)
		{
			ShowPlayersView(room.LobbyId);
		}

		public void OnRoomDelete(RoomRecord room)
		{
			Plugin.Store.DeleteRoom(room.LobbyId);
			RefreshAll();
		}

		public void OnPlayerInvite(string steamId)
		{
			if (!_context.CanInvite)
			{
				Plugin.Log.LogDebug((object)"Invite ignored: not in a live lobby.");
			}
			else if (SteamInviteService.InvitePlayer(steamId))
			{
				RefreshRail();
				WireNavigation();
				RefreshInitialSelection();
			}
		}

		public void OnPlayerAddFriend(string steamId)
		{
			SteamAvatarService.OpenAddFriend(steamId);
		}

		public void OnFriendJoin(FriendRoomInfo friend)
		{
			RoomJoiner.TryJoinLobbyId(friend.LobbyId.ToString(), hidePage: true);
		}

		public void OnFriendInvite(ulong steamId)
		{
			OnPlayerInvite(steamId.ToString());
		}
	}
	public sealed class HistoryStore
	{
		private const int FormatVersion = 3;

		private const char Sep = '\t';

		private const long SaveRetryDelayTicks = 300000000L;

		private static readonly Regex RichTextTag = new Regex("<[^>]+>", RegexOptions.Compiled);

		private readonly string _filePath;

		private readonly string _legacyReTracePath;

		private readonly string _legacyQuickRejoinPath;

		private readonly PluginConfig _config;

		private readonly object _lock = new object();

		private List<RoomRecord> _rooms = new List<RoomRecord>();

		private bool _dirty;

		private bool _loadFailed;

		private bool _storageDisabled;

		private string? _loadBackupSource;

		private bool _loadBackupDone;

		private int _lastSavedRowCount;

		private long _saveRetryAtUtcTicks;

		private float _lastDirtyUnscaledTime;

		public string StoragePath => _filePath;

		public HistoryStore(string configPath, PluginConfig config)
		{
			_config = config;
			string text = Path.Combine(configPath, "RoomHub");
			try
			{
				Directory.CreateDirectory(text);
			}
			catch (Exception ex)
			{
				_storageDisabled = true;
				Plugin.Log.LogError((object)("History directory unavailable, history will not persist: " + ex.Message));
			}
			_filePath = Path.Combine(text, "history.tsv");
			_legacyReTracePath = Path.Combine(configPath, "ReTrace", "history.tsv");
			_legacyQuickRejoinPath = Path.Combine(configPath, "QuickRejoin", "recent_rooms.tsv");
		}

		public IReadOnlyList<RoomRecord> GetRooms()
		{
			lock (_lock)
			{
				return VisibleRoomsUnlocked().Select(CloneRoom).ToList();
			}
		}

		public RoomRecord? GetMostRecent()
		{
			IReadOnlyList<RoomRecord> rooms = GetRooms();
			if (rooms.Count <= 0)
			{
				return null;
			}
			return rooms[0];
		}

		public RoomRecord? GetRoom(string lobbyId)
		{
			lock (_lock)
			{
				RoomRecord roomRecord = _rooms.FirstOrDefault((RoomRecord x) => string.Equals(x.LobbyId, lobbyId, StringComparison.Ordinal));
				return (roomRecord == null) ? null : CloneRoom(roomRecord);
			}
		}

		public IReadOnlyList<PlayerSearchHit> SearchPlayers(string query)
		{
			string text = (query ?? "").Trim();
			if (text.Length == 0)
			{
				return Array.Empty<PlayerSearchHit>();
			}
			lock (_lock)
			{
				Dictionary<string, PlayerSearchHit> dictionary = new Dictionary<string, PlayerSearchHit>(StringComparer.Ordinal);
				foreach (RoomRecord item in VisibleRoomsUnlocked())
				{
					string roomHostName = (string.IsNullOrWhiteSpace(item.HostName) ? item.LobbyId : item.HostName);
					foreach (PlayerRecord player in item.Players)
					{
						if (!string.IsNullOrWhiteSpace(player.SteamId) && player.DisplayName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 && (!dictionary.TryGetValue(player.SteamId, out var value) || player.LastSeenUtcTicks > value.LastSeenUtcTicks))
						{
							dictionary[player.SteamId] = new PlayerSearchHit
							{
								SteamId = player.SteamId,
								DisplayName = player.DisplayName,
								WasHost = player.WasHost,
								LastSeenUtcTicks = player.LastSeenUtcTicks,
								LobbyId = item.LobbyId,
								RoomHostName = roomHostName
							};
						}
					}
				}
				return dictionary.Values.OrderByDescending((PlayerSearchHit h) => h.LastSeenUtcTicks).ToList();
			}
		}

		public void UpsertRoom(string lobbyId, string hostSteamId, string hostName, string lobbyName)
		{
			if (string.IsNullOrWhiteSpace(lobbyId))
			{
				return;
			}
			long ticks = DateTime.UtcNow.Ticks;
			bool flag = false;
			string hostName2;
			lock (_lock)
			{
				RoomRecord roomRecord = _rooms.FirstOrDefault((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal));
				if (roomRecord != null && roomRecord.JoinedAtUtcTicks < ExpireCutoffTicks())
				{
					roomRecord = null;
				}
				if (roomRecord != null)
				{
					if (!IsPlaceholderName(hostName) && IsPlaceholderName(roomRecord.HostName))
					{
						roomRecord.HostName = Sanitize(hostName);
					}
					if (!string.IsNullOrWhiteSpace(lobbyName))
					{
						roomRecord.LobbyName = Sanitize(lobbyName);
					}
					hostName2 = roomRecord.HostName;
				}
				else
				{
					RoomRecord roomRecord2 = new RoomRecord
					{
						LobbyId = lobbyId.Trim(),
						HostSteamId = (hostSteamId ?? "").Trim(),
						HostName = Sanitize(hostName),
						LobbyName = Sanitize(lobbyName),
						JoinedAtUtcTicks = ticks
					};
					_rooms.Insert(0, roomRecord2);
					flag = true;
					hostName2 = roomRecord2.HostName;
				}
				PruneUnlocked(saveIfChanged: false);
				SaveUnlocked();
			}
			Plugin.Log.LogInfo((object)("Room " + (flag ? "recorded (new)" : "updated (rejoin)") + ": lobby=" + lobbyId + ", host='" + hostName2 + "' → " + _filePath));
		}

		public void TouchRoomJoinedNow(string lobbyId, string reason)
		{
			if (string.IsNullOrWhiteSpace(lobbyId))
			{
				return;
			}
			long ticks = DateTime.UtcNow.Ticks;
			long num = 0L;
			bool flag = false;
			lock (_lock)
			{
				RoomRecord roomRecord = _rooms.FirstOrDefault((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal));
				if (roomRecord == null)
				{
					return;
				}
				num = roomRecord.JoinedAtUtcTicks;
				if (ticks > roomRecord.JoinedAtUtcTicks)
				{
					roomRecord.JoinedAtUtcTicks = ticks;
					flag = true;
					MarkDirtyUnlocked();
				}
			}
			if (flag)
			{
				long num2 = ((num > 0) ? ((ticks - num) / 10000000) : 0);
				Plugin.Log.LogInfo((object)$"Room joinedAt touched ({reason}): lobby={lobbyId}, +{num2}s since previous write");
			}
		}

		public void UpsertPlayer(string lobbyId, string steamId, string displayName, bool wasHost)
		{
			if (string.IsNullOrWhiteSpace(lobbyId) || string.IsNullOrWhiteSpace(steamId))
			{
				return;
			}
			lock (_lock)
			{
				RoomRecord roomRecord = _rooms.FirstOrDefault((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal));
				if (roomRecord == null)
				{
					return;
				}
				long ticks = DateTime.UtcNow.Ticks;
				string sid = steamId.Trim();
				PlayerRecord playerRecord = roomRecord.Players.FirstOrDefault((PlayerRecord x) => string.Equals(x.SteamId, sid, StringComparison.Ordinal));
				if (playerRecord == null)
				{
					playerRecord = new PlayerRecord
					{
						SteamId = sid,
						DisplayName = Sanitize(displayName),
						WasHost = false,
						FirstSeenUtcTicks = ticks,
						LastSeenUtcTicks = ticks
					};
					roomRecord.Players.Add(playerRecord);
				}
				else
				{
					if (!string.IsNullOrWhiteSpace(displayName))
					{
						playerRecord.DisplayName = Sanitize(displayName);
					}
					playerRecord.LastSeenUtcTicks = ticks;
				}
				if (string.IsNullOrWhiteSpace(roomRecord.HostSteamId))
				{
					if (wasHost)
					{
						ApplySoleHostUnlocked(roomRecord, sid, playerRecord.DisplayName);
					}
				}
				else if (string.Equals(roomRecord.HostSteamId, sid, StringComparison.Ordinal))
				{
					ApplySoleHostUnlocked(roomRecord, roomRecord.HostSteamId, playerRecord.DisplayName);
				}
				int num = 20;
				if (roomRecord.Players.Count > num)
				{
					roomRecord.Players.Sort((PlayerRecord a, PlayerRecord b) => b.LastSeenUtcTicks.CompareTo(a.LastSeenUtcTicks));
					roomRecord.Players.RemoveRange(num, roomRecord.Players.Count - num);
				}
				MarkDirtyUnlocked();
			}
		}

		public void SetCurrentHost(string lobbyId, string hostSteamId, string? hostDisplayName)
		{
			if (string.IsNullOrWhiteSpace(lobbyId) || string.IsNullOrWhiteSpace(hostSteamId))
			{
				return;
			}
			lock (_lock)
			{
				RoomRecord roomRecord = _rooms.FirstOrDefault((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal));
				if (roomRecord != null)
				{
					ApplySoleHostUnlocked(roomRecord, hostSteamId.Trim(), hostDisplayName);
					MarkDirtyUnlocked();
				}
			}
		}

		private static void ApplySoleHostUnlocked(RoomRecord room, string hostSteamId, string? hostDisplayName)
		{
			if (string.IsNullOrWhiteSpace(room.HostSteamId))
			{
				room.HostSteamId = hostSteamId;
			}
			string hostSteamId2 = room.HostSteamId;
			foreach (PlayerRecord player in room.Players)
			{
				player.WasHost = string.Equals(player.SteamId, hostSteamId2, StringComparison.Ordinal);
			}
			if (!IsPlaceholderName(hostDisplayName) && string.Equals(hostSteamId2, hostSteamId, StringComparison.Ordinal))
			{
				room.HostName = Sanitize(hostDisplayName);
			}
		}

		public void DeleteRoom(string lobbyId)
		{
			if (string.IsNullOrWhiteSpace(lobbyId))
			{
				return;
			}
			lock (_lock)
			{
				int num = _rooms.RemoveAll((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal));
				if (num > 0 && SaveUnlocked())
				{
					Plugin.Log.LogInfo((object)("Deleted room history: " + lobbyId));
				}
			}
		}

		public bool PruneSoloHostedRoom(string? lobbyId, string? localSteamId)
		{
			if (string.IsNullOrWhiteSpace(lobbyId))
			{
				return false;
			}
			string self = (localSteamId ?? "").Trim();
			if (self.Length == 0)
			{
				return false;
			}
			bool flag;
			lock (_lock)
			{
				flag = _rooms.RemoveAll((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal) && IsSoloHostedRoom(r, self)) > 0;
				if (flag)
				{
					SaveUnlocked();
				}
			}
			if (flag)
			{
				Plugin.Log.LogInfo((object)("Pruned solo-hosted room: lobby=" + lobbyId + ", host=" + self + ", reason=no other player was ever recorded"));
			}
			return flag;
		}

		public int PruneSoloHostedRooms(string? localSteamId)
		{
			string self = (localSteamId ?? "").Trim();
			if (self.Length == 0)
			{
				return 0;
			}
			int num = 0;
			string arg = "";
			lock (_lock)
			{
				List<RoomRecord> list = _rooms.Where((RoomRecord r) => IsSoloHostedRoom(r, self)).ToList();
				if (list.Count > 0)
				{
					num = _rooms.RemoveAll(list.Contains);
					arg = string.Join(", ", list.Select((RoomRecord r) => r.LobbyId));
					SaveUnlocked();
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)($"Pruned {num} solo-hosted room(s): lobbies={arg}, host={self}, " + "reason=no other player was ever recorded"));
			}
			return num;
		}

		private static bool IsSoloHostedRoom(RoomRecord room, string localSteamId)
		{
			if (!string.IsNullOrEmpty(room.HostSteamId) && string.Equals(room.HostSteamId, localSteamId, StringComparison.Ordinal))
			{
				return room.Players.Count == 0;
			}
			return false;
		}

		public void TickDeferredSave(float unscaledTime)
		{
			lock (_lock)
			{
				if (_dirty && !_loadFailed && !_storageDisabled && !(unscaledTime - _lastDirtyUnscaledTime < 0.75f))
				{
					SaveUnlocked();
				}
			}
		}

		private void MarkDirtyUnlocked()
		{
			_dirty = true;
			try
			{
				_lastDirtyUnscaledTime = Time.unscaledTime;
			}
			catch
			{
				_lastDirtyUnscaledTime = 0f;
			}
		}

		public void Load()
		{
			lock (_lock)
			{
				_rooms = new List<RoomRecord>();
				_loadFailed = false;
				string loadBackupSource = null;
				try
				{
					if (File.Exists(_filePath))
					{
						loadBackupSource = _filePath;
						LoadFileUnlocked(_filePath);
					}
					else if (File.Exists(_legacyReTracePath))
					{
						loadBackupSource = _legacyReTracePath;
						LoadFileUnlocked(_legacyReTracePath);
						if (_rooms.Count > 0)
						{
							SaveUnlocked();
						}
						Plugin.Log.LogInfo((object)$"Migrated {_rooms.Count} room(s) from ReTrace history.");
					}
					else if (File.Exists(_legacyQuickRejoinPath))
					{
						loadBackupSource = _legacyQuickRejoinPath;
						LoadFileUnlocked(_legacyQuickRejoinPath);
						if (_rooms.Count > 0)
						{
							SaveUnlocked();
						}
						Plugin.Log.LogInfo((object)$"Migrated {_rooms.Count} room(s) from Quick Rejoin TSV.");
					}
					_lastSavedRowCount = _rooms.Count;
					PruneUnlocked(saveIfChanged: true);
					Plugin.Log.LogInfo((object)$"Loaded {_rooms.Count} room(s) from {_filePath}");
				}
				catch (Exception arg)
				{
					_loadFailed = true;
					_rooms = new List<RoomRecord>();
					_loadBackupSource = loadBackupSource;
					_loadBackupDone = false;
					RetryLoadBackupUnlocked();
					Plugin.Log.LogError((object)$"History load failed, writes disabled for this session: {arg}");
				}
			}
		}

		public void Flush()
		{
			lock (_lock)
			{
				if (_loadFailed)
				{
					RetryLoadBackupUnlocked();
				}
				else if (_dirty)
				{
					SaveUnlocked();
				}
			}
		}

		private void LoadFileUnlocked(string path)
		{
			string[] array = File.ReadAllLines(path, Encoding.UTF8);
			if (array.Length == 0)
			{
				return;
			}
			int num = 0;
			if (array[0].StartsWith("QR_RECENT_V", StringComparison.Ordinal) || array[0].StartsWith("RETRACE_V", StringComparison.Ordinal) || array[0].StartsWith("ROOMHISTORY_V", StringComparison.Ordinal))
			{
				num = 1;
			}
			Dictionary<string, RoomRecord> dictionary = new Dictionary<string, RoomRecord>(StringComparer.Ordinal);
			for (int i = num; i < array.Length; i++)
			{
				string text = array[i];
				if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#"))
				{
					continue;
				}
				string[] array2 = text.Split('\t');
				if (array2.Length < 2)
				{
					continue;
				}
				long result4;
				if (array2[0] == "R" && array2.Length >= 6)
				{
					if (long.TryParse(array2[5], out var result) && UtcTicks.IsUsable(result))
					{
						string text2 = Unescape(array2[1]);
						if (!string.IsNullOrWhiteSpace(text2))
						{
							RoomRecord value = new RoomRecord
							{
								LobbyId = text2,
								HostSteamId = Unescape(array2[2]),
								HostName = Sanitize(Unescape(array2[3])),
								LobbyName = Sanitize(Unescape(array2[4])),
								JoinedAtUtcTicks = result
							};
							dictionary[text2] = value;
						}
					}
				}
				else if (array2[0] == "P" && array2.Length >= 7)
				{
					string key = Unescape(array2[1]);
					if (dictionary.TryGetValue(key, out var value2) && long.TryParse(array2[5], out var result2) && UtcTicks.IsUsable(result2))
					{
						if (!long.TryParse(array2[6], out var result3) || !UtcTicks.IsUsable(result3))
						{
							result3 = result2;
						}
						value2.Players.Add(new PlayerRecord
						{
							SteamId = Unescape(array2[2]),
							DisplayName = Sanitize(Unescape(array2[3])),
							WasHost = (array2[4] == "1" || array2[4].Equals("true", StringComparison.OrdinalIgnoreCase)),
							FirstSeenUtcTicks = result2,
							LastSeenUtcTicks = result3
						});
					}
				}
				else if (array2.Length == 4 && !array2[0].Equals("R") && !array2[0].Equals("P") && long.TryParse(array2[3], out result4) && UtcTicks.IsUsable(result4))
				{
					string text3 = Unescape(array2[0]);
					if (!string.IsNullOrWhiteSpace(text3))
					{
						dictionary[text3] = new RoomRecord
						{
							LobbyId = text3,
							HostName = Sanitize(Unescape(array2[1])),
							LobbyName = Sanitize(Unescape(array2[2])),
							JoinedAtUtcTicks = result4
						};
					}
				}
			}
			long ticks = DateTime.UtcNow.Ticks;
			foreach (RoomRecord value3 in dictionary.Values)
			{
				value3.HostName = Sanitize(value3.HostName);
				value3.LobbyName = Sanitize(value3.LobbyName);
				value3.HostSteamId = (value3.HostSteamId ?? "").Trim();
				if (value3.JoinedAtUtcTicks > ticks)
				{
					value3.JoinedAtUtcTicks = ticks;
				}
				foreach (PlayerRecord player in value3.Players)
				{
					if (player.FirstSeenUtcTicks > ticks)
					{
						player.FirstSeenUtcTicks = ticks;
					}
					if (player.LastSeenUtcTicks > ticks)
					{
						player.LastSeenUtcTicks = ticks;
					}
				}
				if (!string.IsNullOrWhiteSpace(value3.HostSteamId))
				{
					ApplySoleHostUnlocked(value3, value3.HostSteamId, value3.HostName);
					continue;
				}
				List<PlayerRecord> list = value3.Players.Where((PlayerRecord p) => p.WasHost).ToList();
				if (list.Count > 1)
				{
					PlayerRecord playerRecord = list.OrderByDescending((PlayerRecord p) => p.LastSeenUtcTicks).First();
					ApplySoleHostUnlocked(value3, playerRecord.SteamId, playerRecord.DisplayName);
				}
			}
			_rooms = dictionary.Values.OrderByDescending((RoomRecord r) => r.JoinedAtUtcTicks).ToList();
		}

		private long ExpireCutoffTicks()
		{
			int num = Math.Max(1, _config.HistoryExpireHours.Value);
			return DateTime.UtcNow.AddHours(-num).Ticks;
		}

		private List<RoomRecord> VisibleRoomsUnlocked()
		{
			int count = Math.Max(1, _config.MaxRecentRooms.Value);
			long cutoff = ExpireCutoffTicks();
			return (from r in _rooms
				where r != null && !string.IsNullOrWhiteSpace(r.LobbyId) && r.JoinedAtUtcTicks >= cutoff
				group r by r.LobbyId into g
				select g.OrderByDescending((RoomRecord x) => x.JoinedAtUtcTicks).First() into r
				orderby r.JoinedAtUtcTicks descending
				select r).Take(count).ToList();
		}

		private void PruneUnlocked(bool saveIfChanged)
		{
			int count = _rooms.Count;
			_rooms = VisibleRoomsUnlocked();
			if (saveIfChanged && _rooms.Count != count)
			{
				SaveUnlocked();
			}
		}

		private bool SaveUnlocked()
		{
			if (_loadFailed)
			{
				RetryLoadBackupUnlocked();
				return false;
			}
			if (_storageDisabled)
			{
				return false;
			}
			long ticks = DateTime.UtcNow.Ticks;
			if (_saveRetryAtUtcTicks != 0L && ticks < _saveRetryAtUtcTicks)
			{
				return false;
			}
			string text = _filePath + ".tmp";
			try
			{
				string directoryName = Path.GetDirectoryName(_filePath);
				if (!string.IsNullOrEmpty(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				if (_rooms.Count < _lastSavedRowCount)
				{
					BackupCurrentUnlocked();
				}
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append("ROOMHISTORY_V").Append(3).Append('\n');
				stringBuilder.Append("# R lobbyId hostSteamId hostName lobbyName joinedTicks\n");
				stringBuilder.Append("# P lobbyId steamId displayName wasHost firstTicks lastTicks\n");
				foreach (RoomRecord item in _rooms.OrderByDescending((RoomRecord x) => x.JoinedAtUtcTicks))
				{
					stringBuilder.Append("R").Append('\t').Append(Escape(item.LobbyId))
						.Append('\t')
						.Append(Escape(item.HostSteamId))
						.Append('\t')
						.Append(Escape(item.HostName))
						.Append('\t')
						.Append(Escape(item.LobbyName))
						.Append('\t')
						.Append(item.JoinedAtUtcTicks)
						.Append('\n');
					foreach (PlayerRecord item2 in item.Players.OrderByDescending((PlayerRecord x) => x.LastSeenUtcTicks))
					{
						stringBuilder.Append("P").Append('\t').Append(Escape(item.LobbyId))
							.Append('\t')
							.Append(Escape(item2.SteamId))
							.Append('\t')
							.Append(Escape(item2.DisplayName))
							.Append('\t')
							.Append(item2.WasHost ? "1" : "0")
							.Append('\t')
							.Append(item2.FirstSeenUtcTicks)
							.Append('\t')
							.Append(item2.LastSeenUtcTicks)
							.Append('\n');
					}
				}
				File.WriteAllText(text, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				if (File.Exists(_filePath))
				{
					File.Replace(text, _filePath, null);
				}
				else
				{
					File.Move(text, _filePath);
				}
				_dirty = false;
				_lastSavedRowCount = _rooms.Count;
				_saveRetryAtUtcTicks = 0L;
				return true;
			}
			catch (Exception ex)
			{
				_saveRetryAtUtcTicks = ticks + 300000000;
				_dirty = true;
				TryDelete(text);
				Plugin.Log.LogError((object)$"History save failed, retrying in {30L}s: {ex.Message}");
				return false;
			}
		}

		private void BackupCurrentUnlocked()
		{
			try
			{
				if (File.Exists(_filePath))
				{
					File.Copy(_filePath, _filePath + ".bak", overwrite: true);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("History backup failed: " + ex.Message));
			}
		}

		private string? RetryLoadBackupUnlocked()
		{
			if (_loadBackupDone || _loadBackupSource == null)
			{
				return null;
			}
			string text = BackupUnreadableUnlocked(_loadBackupSource, quiet: true);
			if (text == null)
			{
				return null;
			}
			_loadBackupDone = true;
			Plugin.Log.LogWarning((object)("Unreadable history copied to " + text));
			return text;
		}

		private string? BackupUnreadableUnlocked(string source, bool quiet)
		{
			try
			{
				if (!File.Exists(source))
				{
					return null;
				}
				string text = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture);
				string text2 = source + ".bak-" + text;
				int num = 1;
				while (File.Exists(text2) && num < 100)
				{
					text2 = $"{source}.bak-{text}-{num}";
					num++;
				}
				File.Copy(source, text2, overwrite: false);
				return text2;
			}
			catch (Exception ex)
			{
				if (!quiet)
				{
					Plugin.Log.LogWarning((object)("History backup of unreadable file failed: " + ex.Message));
				}
				return null;
			}
		}

		private static void TryDelete(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}

		internal static bool IsPlaceholderName(string? name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return true;
			}
			if (!string.Equals(name, Loc.T("ROOMHISTORY_UNKNOWN_HOST"), StringComparison.Ordinal))
			{
				return string.Equals(name, Loc.T("ROOMHISTORY_UNKNOWN_PLAYER"), StringComparison.Ordinal);
			}
			return true;
		}

		private static RoomRecord CloneRoom(RoomRecord r)
		{
			RoomRecord roomRecord = new RoomRecord
			{
				LobbyId = r.LobbyId,
				HostSteamId = r.HostSteamId,
				HostName = r.HostName,
				LobbyName = r.LobbyName,
				JoinedAtUtcTicks = r.JoinedAtUtcTicks
			};
			foreach (PlayerRecord player in r.Players)
			{
				roomRecord.Players.Add(new PlayerRecord
				{
					SteamId = player.SteamId,
					DisplayName = player.DisplayName,
					WasHost = player.WasHost,
					FirstSeenUtcTicks = player.FirstSeenUtcTicks,
					LastSeenUtcTicks = player.LastSeenUtcTicks
				});
			}
			return roomRecord;
		}

		private static string Sanitize(string? s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			string text = RichTextTag.Replace(s, "");
			return text.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' ')
				.Trim();
		}

		private static string Escape(string? s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			return s.Replace("\\", "\\\\").Replace("\t", "\\t").Replace("\n", "\\n")
				.Replace("\r", "\\r");
		}

		private static string Unescape(string? s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			for (int i = 0; i < s.Length; i++)
			{
				if (s[i] == '\\' && i + 1 < s.Length)
				{
					char c = s[++i];
					StringBuilder stringBuilder2 = stringBuilder;
					stringBuilder2.Append(c switch
					{
						't' => '\t', 
						'n' => '\n', 
						'r' => '\r', 
						'\\' => '\\', 
						_ => c, 
					});
				}
				else
				{
					stringBuilder.Append(s[i]);
				}
			}
			return stringBuilder.ToString();
		}
	}
	internal static class HistoryUi
	{
		private static HistoryPageController? _page;

		internal static bool PageOpenThisFrame { get; private set; }

		public static bool IsOpen
		{
			get
			{
				if (_page != null)
				{
					return _page.IsPageOpen;
				}
				return false;
			}
		}

		public static bool IsTitleScene
		{
			get
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				Scene activeScene = SceneManager.GetActiveScene();
				if (((Scene)(ref activeScene)).IsValid())
				{
					return string.Equals(((Scene)(ref activeScene)).name, "Title", StringComparison.Ordinal);
				}
				return false;
			}
		}

		public static bool IsInLiveLobby
		{
			get
			{
				try
				{
					IMatchmakingAPI matchmaking = NetCode.Matchmaking;
					return matchmaking != null && matchmaking.InLobby;
				}
				catch
				{
					return false;
				}
			}
		}

		public static bool CanShowInvite
		{
			get
			{
				if (!IsTitleScene)
				{
					return IsInLiveLobby;
				}
				return false;
			}
		}

		public static void Open()
		{
			try
			{
				PauseMenuHook.ClosePauseMenuIfOpen();
				EnsureController();
				_page.Open();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"HistoryUi.Open: {arg}");
				DropPage();
			}
		}

		public static void Hide()
		{
			try
			{
				_page?.Hide();
			}
			catch
			{
			}
		}

		public static void Toggle()
		{
			if (IsOpen)
			{
				Hide();
			}
			else
			{
				Open();
			}
		}

		public static void DropPage()
		{
			bool flag = _page != null && _page.HasLivePage;
			try
			{
				_page?.Discard();
			}
			catch
			{
			}
			_page = null;
			if (!flag)
			{
				UiSprites.Shutdown();
			}
		}

		public static void OnSceneLoaded()
		{
			DropPage();
		}

		public static void Tick()
		{
			PageOpenThisFrame = _page != null && _page.IsPageOpen;
			if (_page == null)
			{
				return;
			}
			if (!_page.HasLivePage)
			{
				DropPage();
			}
			else if (_page.IsPageOpen)
			{
				if (Input.GetKeyDown((KeyCode)27) || ConsumeGamepadBack())
				{
					_page.HandleBackOrClose();
					return;
				}
				_page.TickSelection();
				_page.TickAvatarRefresh();
			}
		}

		private static bool ConsumeGamepadBack()
		{
			try
			{
				UIInputHandler instance = Singleton<UIInputHandler>.Instance;
				if ((Object)(object)instance != (Object)null && instance.cancelWasPressed)
				{
					instance.cancelWasPressed = false;
					return true;
				}
			}
			catch
			{
			}
			try
			{
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter != (Object)null && localCharacter.input.pauseWasPressed)
				{
					localCharacter.input.pauseWasPressed = false;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		public static bool IsTextInputFocused()
		{
			try
			{
				return _page != null && _page.IsSearchFocused;
			}
			catch
			{
				return false;
			}
		}

		private static void EnsureController()
		{
			if (_page != null && !_page.HasLivePage)
			{
				DropPage();
			}
			if (_page == null)
			{
				_page = new HistoryPageController();
			}
		}
	}
	[HarmonyPatch(typeof(GUIManager), "UpdatePaused")]
	internal static class PauseInputSuppressor
	{
		[HarmonyPrefix]
		internal static bool Prefix()
		{
			return !HistoryUi.PageOpenThisFrame;
		}
	}
	internal static class HistoryUiFont
	{
		private static TMP_FontAsset? _font;

		public static void Reset()
		{
			_font = null;
		}

		public static TMP_FontAsset? Resolve()
		{
			if ((Object)(object)_font != (Object)null)
			{
				return _font;
			}
			FontFallbackSwapper instance = FontFallbackSwapper.instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.mainBaseFont != (Object)null)
			{
				Adopt(instance.mainBaseFont, "FontFallbackSwapper.mainBaseFont");
				return _font;
			}
			TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset;
			if ((Object)(object)defaultFontAsset != (Object)null)
			{
				Adopt(defaultFontAsset, "TMP_Settings.defaultFontAsset");
				return _font;
			}
			AdoptFromScene();
			return _font;
		}

		public static void Apply(TMP_Text? text)
		{
			if ((Object)(object)text == (Object)null)
			{
				return;
			}
			TMP_FontAsset val = Resolve();
			if (!((Object)(object)val == (Object)null) && !((Object)(object)text.font == (Object)(object)val))
			{
				text.font = val;
				if ((Object)(object)((TMP_Asset)val).material != (Object)null)
				{
					text.fontSharedMaterial = ((TMP_Asset)val).material;
				}
			}
		}

		public static void Apply(PeakText? peakText)
		{
			Apply((TMP_Text?)(object)((peakText != null) ? peakText.TextMesh : null));
		}

		private static void Adopt(TMP_FontAsset font, string source)
		{
			_font = font;
			Plugin.Log.LogInfo((object)("UI font from " + source + ": " + ((Object)font).name));
		}

		private static void AdoptFromScene()
		{
			try
			{
				TextMeshProUGUI[] array = Object.FindObjectsByType<TextMeshProUGUI>((FindObjectsSortMode)0);
				foreach (TextMeshProUGUI val in array)
				{
					if (!((Object)(object)val == (Object)null) && !((Object)(object)((TMP_Text)val).font == (Object)null) && !string.IsNullOrEmpty(((TMP_Text)val).text) && ((Component)val).gameObject.activeInHierarchy)
					{
						Adopt(((TMP_Text)val).font, "scene label " + ((Object)val).name);
						break;
					}
				}
			}
			catch
			{
			}
		}
	}
	internal static class JoinGate
	{
		private sealed class Entry
		{
			public long CooldownUntilUtcTicks;
		}

		public const double MaxLobbyAgeHours = 2.5;

		private const double JoinFailCooldownSec = 600.0;

		private static readonly long JoinFailCooldownTicks = 6000000000L;

		private static readonly long MaxLobbyAgeTicks = 90000000000L;

		private static readonly Dictionary<ulong, Entry> _cooldowns = new Dictionary<ulong, Entry>();

		public static event Action? StateChanged;

		public static bool IsJoinAllowed(string lobbyId, long joinedAtUtcTicks)
		{
			if (joinedAtUtcTicks > 0 && joinedAtUtcTicks <= DateTime.MaxValue.Ticks)
			{
				long num = DateTime.UtcNow.Ticks - joinedAtUtcTicks;
				if (num > MaxLobbyAgeTicks)
				{
					return false;
				}
			}
			if (ulong.TryParse(lobbyId, out var result) && result != 0L && _cooldowns.TryGetValue(result, out Entry value))
			{
				if (DateTime.UtcNow.Ticks < value.CooldownUntilUtcTicks)
				{
					return false;
				}
				_cooldowns.Remove(result);
			}
			return true;
		}

		public static void MarkJoinFailed(ulong lobbyId)
		{
			if (lobbyId == 0L)
			{
				return;
			}
			_cooldowns[lobbyId] = new Entry
			{
				CooldownUntilUtcTicks = DateTime.UtcNow.Ticks + JoinFailCooldownTicks
			};
			try
			{
				JoinGate.StateChanged?.Invoke();
			}
			catch
			{
			}
		}
	}
	internal static class LobbyJoinFeedback
	{
		private static Callback<LobbyEnter_t>? _callback;

		private static Callback<LobbyDataUpdate_t>? _lobbyDataCallback;

		private static bool _inited;

		private static fl