using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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 Photon.Pun; using Photon.Realtime; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; 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+77d72fe7aa78cbcd5bd93ba2ab02923e6b3c14a4")] [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, Unknown } public readonly struct FriendRoomInfo { public readonly ulong SteamId; public readonly string Name; public readonly ulong LobbyId; public readonly FriendGameMode Mode; public FriendRoomInfo(ulong steamId, string name, ulong lobbyId, FriendGameMode mode) { SteamId = steamId; Name = name ?? ""; LobbyId = lobbyId; Mode = mode; } } internal static class FriendRoomsService { public const uint PeakAppId = 3527290u; private const float RefreshIntervalSec = 10f; private static Callback? _personaChanged; private static Callback? _richPresenceUpdate; private static float _nextRefresh; private static List? _cache; private static float _lastNotify; private const float NotifyMinIntervalSec = 1.5f; private static bool _inited; private static readonly Dictionary _lastRpRequest = new Dictionary(); private const float RpRequestMinIntervalSec = 30f; public static event Action? Changed; public static void Init() { if (_inited) { return; } try { _personaChanged = Callback.Create((DispatchDelegate)OnPersonaChanged); _richPresenceUpdate = Callback.Create((DispatchDelegate)OnFriendRichPresenceUpdate); _inited = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("FriendRoomsService init failed: " + ex.Message)); } } public static IReadOnlyList GetFriends() { RefreshIfDue(force: false); IReadOnlyList cache = _cache; return cache ?? Array.Empty(); } private static void RefreshIfDue(bool force) { //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0114: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0070: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: 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) if (!_inited || (!force && _cache != null && Time.unscaledTime < _nextRefresh)) { return; } _nextRefresh = Time.unscaledTime + 10f; try { List list = new List(); 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) { continue; } bool flag = false; ulong lobbyId = 0uL; FriendGameMode mode = FriendGameMode.MainMenu; if (SteamFriends.GetFriendGamePlayed(friendByIndex, ref val) && ((CGameID)(ref val.m_gameID)).AppID().m_AppId == 3527290) { flag = true; try { float unscaledTime = Time.unscaledTime; if (!_lastRpRequest.TryGetValue(friendByIndex.m_SteamID, out var value) || unscaledTime - value >= 30f) { _lastRpRequest[friendByIndex.m_SteamID] = unscaledTime; SteamFriends.RequestFriendRichPresence(friendByIndex); } } catch { } if (((CSteamID)(ref val.m_steamIDLobby)).IsValid() && val.m_steamIDLobby.m_SteamID != 0L) { lobbyId = val.m_steamIDLobby.m_SteamID; mode = FriendGameMode.Joinable; } else { int num = 0; try { num = SteamFriends.GetFriendRichPresenceKeyCount(friendByIndex); } catch { } if (num == 0) { mode = FriendGameMode.Unknown; } else { try { string friendRichPresence = SteamFriends.GetFriendRichPresence(friendByIndex, "steam_display"); string friendRichPresence2 = SteamFriends.GetFriendRichPresence(friendByIndex, "players"); string friendRichPresence3 = SteamFriends.GetFriendRichPresence(friendByIndex, "max_players"); mode = (string.Equals(friendRichPresence, "#Status_MainMenu", StringComparison.Ordinal) ? FriendGameMode.MainMenu : (string.Equals(friendRichPresence3, "1", StringComparison.Ordinal) ? FriendGameMode.Solo : (string.Equals(friendRichPresence2, "1", StringComparison.Ordinal) ? FriendGameMode.MainMenu : ((!string.IsNullOrEmpty(friendRichPresence3)) ? FriendGameMode.InviteOnly : FriendGameMode.MainMenu)))); } catch { } } } } string name = SteamFriends.GetFriendPersonaName(friendByIndex) ?? ""; if (flag) { list.Add(new FriendRoomInfo(friendByIndex.m_SteamID, name, lobbyId, mode)); } } _cache = list.OrderByDescending((FriendRoomInfo f) => f.LobbyId != 0).ThenBy((FriendRoomInfo f) => f.Name, StringComparer.OrdinalIgnoreCase).ToList(); } catch (Exception ex) { Plugin.Log.LogDebug((object)("FriendRoomsService refresh failed: " + ex.Message)); } } private static void OnPersonaChanged(PersonaStateChange_t cb) { _cache = null; float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastNotify < 1.5f) { return; } _lastNotify = unscaledTime; try { FriendRoomsService.Changed?.Invoke(); } catch { } } private static void OnFriendRichPresenceUpdate(FriendRichPresenceUpdate_t cb) { _cache = null; float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastNotify < 1.5f) { return; } _lastNotify = unscaledTime; try { FriendRoomsService.Changed?.Invoke(); } catch { } } } internal sealed class HistoryPageController { private const string SearchViewId = "__search__"; private const float FriendPanelWidth = 400f; private const float ListWidth = 800f; private const float RoomRowHeight = 120f; private const float PlayerRowHeight = 110f; private const float DoubleClickSeconds = 0.4f; private PeakCustomPage? _page; private PeakScrollableContent? _list; private PeakScrollableContent? _friendList; private PeakText? _titleText; private PeakText? _hintText; private PeakText? _hintText2; private PeakText? _deleteBanner; private PeakText? _friendPanelTitle; private PeakMenuButton? _trashButton; private PeakMenuButton? _backButton; private PeakMenuButton? _searchButton; private PeakMenuButton? _closeButton; private PeakTextInput? _searchInput; private bool _deleteMode; private string? _viewingLobbyId; private string? _searchQuery; private string? _lastClickLobbyId; private float _lastClickTime; private readonly List _avatarRefreshTargets = new List(); private HistoryRowFactory? _rows; private HistoryRowFactory? _friendRows; private bool _cursorCaptured; private CursorLockMode _prevLockMode = (CursorLockMode)1; private bool _prevCursorVisible; 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); private bool _openedViaMenuWindow; 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 void Hide() { if (!HasLivePage) { _openedViaMenuWindow = false; ReleaseGameplayInputBlock(); return; } try { CloseMenuWindow(); } catch { } _openedViaMenuWindow = false; try { if (HasLivePage && ((Component)_page).gameObject.activeInHierarchy) { ((MenuWindow)_page).Hide(); } } catch { } ReleaseGameplayInputBlock(); } public void Discard() { try { if (HasLivePage) { CloseMenuWindow(); } } catch { } ReleaseGameplayInputBlock(); try { if (HasLivePage) { Object.Destroy((Object)(object)((Component)_page).gameObject); } } catch { } _page = null; _list = null; _friendList = null; _friendPanelTitle = null; _titleText = null; _hintText = null; _hintText2 = null; _deleteBanner = null; _trashButton = null; _backButton = null; _closeButton = null; _searchButton = null; _searchInput = null; _rows = null; _friendRows = null; _avatarRefreshTargets.Clear(); _deleteMode = false; _viewingLobbyId = null; _searchQuery = null; _openedViaMenuWindow = false; LocalizationSync.Unregister(OnGameLanguageChanged); UnsubscribeExternalEvents(); } public void Open() { _deleteMode = false; _viewingLobbyId = null; HistoryUiFont.CaptureFromMenuButton(); EnsurePage(); if (!HasLivePage) { return; } ShowRoomsView(); try { _page.BlocksPlayerInput = true; _page.ShowCursorWhileOpen = true; _page.CloseOnUICancel = false; _page.CloseOnPause = false; if (!OpenMenuWindow()) { if (!HistoryUi.IsTitleScene) { Plugin.Log.LogError((object)"MenuWindow.Open unavailable in-game — aborting open."); Discard(); return; } ((MenuWindow)_page).Show(); _openedViaMenuWindow = true; Plugin.Log.LogWarning((object)"MenuWindow.Open unavailable — using Show on title."); } else { _openedViaMenuWindow = true; } CaptureGameplayInputBlock(); EnsurePageBlocksRaycasts(); } catch (Exception arg) { Plugin.Log.LogError((object)$"History page Open failed: {arg}"); Discard(); } } 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 { } } } private void CaptureGameplayInputBlock() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!_cursorCaptured && !HistoryUi.IsTitleScene) { _prevLockMode = (CursorLockMode)1; _prevCursorVisible = false; Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; _cursorCaptured = true; } } private void ReleaseGameplayInputBlock() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (!_cursorCaptured) { return; } _cursorCaptured = false; try { Cursor.lockState = _prevLockMode; Cursor.visible = _prevCursorVisible; } catch { } } public void TickInputBlock() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!IsPageOpen || HistoryUi.IsTitleScene) { if (_cursorCaptured && !IsPageOpen) { ReleaseGameplayInputBlock(); } return; } if (!_cursorCaptured) { CaptureGameplayInputBlock(); } if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } if (!Cursor.visible) { Cursor.visible = true; } } private void EnsurePageBlocksRaycasts() { if (!HasLivePage) { return; } try { GameObject gameObject = ((Component)_page).gameObject; Canvas component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null && component.sortingOrder < 500) { component.sortingOrder = 500; } if ((Object)(object)gameObject.GetComponent() == (Object)null) { gameObject.AddComponent(); } Image[] componentsInChildren = gameObject.GetComponentsInChildren(true); foreach (Image val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && (((Object)((Component)val).gameObject).name == "Background" || (Object)(object)((Component)val).transform.parent == (Object)(object)gameObject.transform)) { ((Graphic)val).raycastTarget = true; } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("EnsurePageBlocksRaycasts: " + ex.Message)); } } public void HandleBackOrClose() { if (IsPageOpen) { if (_viewingLobbyId != null) { ShowRoomsView(); } else if (_deleteMode) { _deleteMode = false; RefreshCurrentView(); } else { Hide(); } } } public void TickAvatarRefresh() { //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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) if (!IsPageOpen || Time.frameCount % 45 != 0) { return; } for (int num = _avatarRefreshTargets.Count - 1; num >= 0; num--) { GameObject val = _avatarRefreshTargets[num]; if ((Object)(object)val == (Object)null) { _avatarRefreshTargets.RemoveAt(num); } else { Image component = val.GetComponent(); if ((Object)(object)component == (Object)null || !((Object)val).name.StartsWith("Avatar_", StringComparison.Ordinal)) { _avatarRefreshTargets.RemoveAt(num); } else { string steamIdStr = ((Object)val).name.Substring("Avatar_".Length); Texture2D avatar = SteamAvatarService.GetAvatar(steamIdStr); if (!((Object)(object)avatar == (Object)null)) { for (int num2 = val.transform.childCount - 1; num2 >= 0; num2--) { Transform child = val.transform.GetChild(num2); if (((Object)child).name != "Border") { Object.Destroy((Object)(object)((Component)child).gameObject); } } if ((Object)(object)component.sprite != (Object)null) { Sprite sprite = component.sprite; component.sprite = null; Object.Destroy((Object)(object)sprite); } component.sprite = Sprite.Create(avatar, new Rect(0f, 0f, (float)((Texture)avatar).width, (float)((Texture)avatar).height), new Vector2(0.5f, 0.5f)); ((Graphic)component).color = Color.white; component.preserveAspect = true; _avatarRefreshTargets.RemoveAt(num); } } } } } private void EnsurePage() { //IL_04f8: Unknown result type (might be due to invalid IL or missing references) //IL_050c: Unknown result type (might be due to invalid IL or missing references) //IL_0520: Unknown result type (might be due to invalid IL or missing references) //IL_053b: Unknown result type (might be due to invalid IL or missing references) //IL_0545: Expected O, but got Unknown //IL_056f: Unknown result type (might be due to invalid IL or missing references) //IL_0583: Unknown result type (might be due to invalid IL or missing references) //IL_0597: Unknown result type (might be due to invalid IL or missing references) //IL_05ab: Unknown result type (might be due to invalid IL or missing references) //IL_0090: 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) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0152: 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_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: 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_034a: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_0372: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Expected O, but got Unknown //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0424: Expected O, but got Unknown //IL_067a: Unknown result type (might be due to invalid IL or missing references) //IL_069e: Unknown result type (might be due to invalid IL or missing references) //IL_06b2: Unknown result type (might be due to invalid IL or missing references) //IL_06c6: Unknown result type (might be due to invalid IL or missing references) //IL_06da: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Unknown result type (might be due to invalid IL or missing references) //IL_0462: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Unknown result type (might be due to invalid IL or missing references) //IL_077c: Unknown result type (might be due to invalid IL or missing references) //IL_0790: Unknown result type (might be due to invalid IL or missing references) //IL_07a4: Unknown result type (might be due to invalid IL or missing references) //IL_07b8: Unknown result type (might be due to invalid IL or missing references) //IL_08cc: Unknown result type (might be due to invalid IL or missing references) //IL_08e0: Unknown result type (might be due to invalid IL or missing references) //IL_08f4: Unknown result type (might be due to invalid IL or missing references) //IL_090f: Unknown result type (might be due to invalid IL or missing references) //IL_0919: Expected O, but got Unknown if (!HasLivePage) { if ((Object)(object)_page != (Object)null) { Discard(); } UiButtonFactory.EnsureTemplate(); _page = MenuAPI.CreatePageWithBackground("RoomHub Page"); _page.CloseOnUICancel = false; _page.BlocksPlayerInput = true; _page.ShowCursorWhileOpen = true; EnsurePageBlocksRaycasts(); _titleText = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_PAGE_TITLE")).SetFontSize(44f), ((Component)_page).transform), new Vector2(0.5f, 1f)), new Vector2(0.5f, 1f)), new Vector2(0f, -36f)); HistoryUiFont.Apply(_titleText); HistoryUiFont.ForceCenteredLabel(_titleText, 900f, 50f, new Vector2(0f, -36f)); _hintText = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText("").SetFontSize(20f).SetColor(new Color(1f, 0.92f, 0.35f)), ((Component)_page).transform), new Vector2(0.5f, 1f)), new Vector2(0.5f, 1f)), new Vector2(0f, -88f)); HistoryUiFont.Apply(_hintText); HistoryUiFont.ForceCenteredLabel(_hintText, 880f, 36f, new Vector2(0f, -88f)); _hintText2 = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText("").SetFontSize(18f).SetColor(new Color(0.75f, 0.9f, 1f)), ((Component)_page).transform), new Vector2(0.5f, 1f)), new Vector2(0.5f, 1f)), new Vector2(0f, -118f)); HistoryUiFont.Apply(_hintText2); HistoryUiFont.ForceCenteredLabel(_hintText2, 880f, 32f, new Vector2(0f, -118f)); _deleteBanner = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_DELETE_BANNER")).SetFontSize(22f).SetColor(new Color(1f, 0.35f, 0.3f)), ((Component)_page).transform), new Vector2(0.5f, 1f)), new Vector2(0.5f, 1f)), new Vector2(0f, -88f)); HistoryUiFont.Apply(_deleteBanner); HistoryUiFont.ForceCenteredLabel(_deleteBanner, 900f, 40f, new Vector2(0f, -88f)); ((Component)_deleteBanner).gameObject.SetActive(false); _backButton = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe("← " + Loc.T("ROOMHISTORY_BACK")), ((Component)_page).transform), new Vector2(0f, 1f)), new Vector2(0f, 1f)), new Vector2(36f, -36f)).SetWidth(170f).OnClick(new UnityAction(ShowRoomsView)); ((Component)_backButton).gameObject.SetActive(false); _trashButton = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_DELETE_MODE_ON")), ((Component)_page).transform), new Vector2(1f, 1f)), new Vector2(1f, 1f)), new Vector2(-28f, -36f)).SetWidth(200f).OnClick(new UnityAction(ToggleDeleteMode)); try { _searchInput = ElementExtensions.SetSize(ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateTextInput("RoomHubSearchInput"), ((Component)_page).transform), new Vector2(1f, 1f)), new Vector2(1f, 1f)), new Vector2(-240f, -100f)), new Vector2(220f, 56f)).SetPlaceholder(Loc.T("ROOMHISTORY_SEARCH_PLACEHOLDER")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Search input create failed (game not fully ready?): " + ex.Message)); _searchInput = null; } _searchButton = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_SEARCH")), ((Component)_page).transform), new Vector2(1f, 1f)), new Vector2(1f, 1f)), new Vector2(-28f, -100f)).SetWidth(140f).OnClick(new UnityAction(RunPlayerSearch)); _list = ElementExtensions.SetSize(ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateScrollableContent("RoomHubList"), ((Component)_page).transform), new Vector2(0.5f, 1f)), new Vector2(0.5f, 1f)), new Vector2(0f, -200f)), new Vector2(800f, 600f)); VerticalLayoutGroup component = ((Component)_list.Content).GetComponent(); if ((Object)(object)component != (Object)null) { ((HorizontalOrVerticalLayoutGroup)component).spacing = 8f; ((LayoutGroup)component).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)component).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)component).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)component).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)component).childForceExpandHeight = false; } _rows = new HistoryRowFactory((Transform)(object)_list.Content, 800f, 120f, 110f, _avatarRefreshTargets, RefreshCurrentView, ShowPlayersView, OnRoomRowClicked); _friendPanelTitle = ElementExtensions.SetSize(ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_FRIENDS_TITLE")).SetFontSize(32f).SetColor(new Color(1f, 0.92f, 0.35f)), ((Component)_page).transform), new Vector2(0f, 1f)), new Vector2(0.5f, 1f)), new Vector2(240f, -100f)), new Vector2(400f, 40f)); HistoryUiFont.Apply(_friendPanelTitle); if ((Object)(object)_friendPanelTitle.TextMesh != (Object)null) { ((TMP_Text)_friendPanelTitle.TextMesh).alignment = (TextAlignmentOptions)514; ((TMP_Text)_friendPanelTitle.TextMesh).enableAutoSizing = true; ((TMP_Text)_friendPanelTitle.TextMesh).fontSizeMin = 20f; ((TMP_Text)_friendPanelTitle.TextMesh).fontSizeMax = 36f; } _friendList = ElementExtensions.SetSize(ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateScrollableContent("RoomHubFriendList"), ((Component)_page).transform), new Vector2(0f, 1f)), new Vector2(0f, 1f)), new Vector2(40f, -150f)), new Vector2(400f, 650f)); VerticalLayoutGroup component2 = ((Component)_friendList.Content).GetComponent(); if ((Object)(object)component2 != (Object)null) { ((HorizontalOrVerticalLayoutGroup)component2).spacing = 8f; ((LayoutGroup)component2).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false; } ((Component)_friendList).gameObject.SetActive(false); _friendRows = new HistoryRowFactory((Transform)(object)_friendList.Content, 400f, 120f, 110f, _avatarRefreshTargets, RefreshCurrentView, ShowPlayersView, OnRoomRowClicked); JoinGate.StateChanged += OnExternalStateChanged; FriendRoomsService.Changed += OnExternalStateChanged; LocalizationSync.Register(OnGameLanguageChanged); _closeButton = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_CLOSE")), ((Component)_page).transform), new Vector2(0.5f, 0f)), new Vector2(0.5f, 0f)), new Vector2(0f, 80f)).SetWidth(220f).OnClick((UnityAction)delegate { _deleteMode = false; Hide(); }); } } private void OnGameLanguageChanged() { try { RefreshStaticChrome(); if (IsPageOpen) { RefreshCurrentView(); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("OnGameLanguageChanged: " + ex.Message)); } } private void RefreshStaticChrome() { PeakText? friendPanelTitle = _friendPanelTitle; if ((Object)(object)((friendPanelTitle != null) ? friendPanelTitle.TextMesh : null) != (Object)null) { ((TMP_Text)_friendPanelTitle.TextMesh).text = Loc.T("ROOMHISTORY_FRIENDS_TITLE"); HistoryUiFont.Apply(_friendPanelTitle); ((TMP_Text)_friendPanelTitle.TextMesh).ForceMeshUpdate(true, false); } if ((Object)(object)_closeButton != (Object)null) { try { _closeButton.SetText(Loc.T("ROOMHISTORY_CLOSE")); } catch { } } } private void ToggleDeleteMode() { if (_viewingLobbyId == null) { _deleteMode = !_deleteMode; RefreshCurrentView(); } } private void ShowRoomsView() { _viewingLobbyId = null; _searchQuery = null; _deleteMode = false; RefreshCurrentView(); } private void ShowPlayersView(string lobbyId) { _viewingLobbyId = lobbyId; _searchQuery = null; _deleteMode = false; RefreshCurrentView(); } private void UnsubscribeExternalEvents() { JoinGate.StateChanged -= OnExternalStateChanged; FriendRoomsService.Changed -= OnExternalStateChanged; } private void OnExternalStateChanged() { if (!IsPageOpen) { return; } try { RefreshCurrentView(); } catch { } } private void RunPlayerSearch() { string text = ""; try { PeakTextInput? searchInput = _searchInput; if ((Object)(object)((searchInput != null) ? searchInput.InputField : null) != (Object)null) { text = _searchInput.InputField.text ?? ""; } } catch { } text = text.Trim(); if (text.Length == 0) { ShowRoomsView(); return; } _searchQuery = text; _viewingLobbyId = "__search__"; _deleteMode = false; RefreshCurrentView(); } private void RefreshCurrentView() { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_011e: 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_045b: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_057e: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Unknown result type (might be due to invalid IL or missing references) //IL_0545: Unknown result type (might be due to invalid IL or missing references) //IL_0649: Unknown result type (might be due to invalid IL or missing references) //IL_0678: Unknown result type (might be due to invalid IL or missing references) //IL_05b8: Unknown result type (might be due to invalid IL or missing references) //IL_06c2: Unknown result type (might be due to invalid IL or missing references) //IL_0407: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_06fc: Expected O, but got Unknown //IL_0751: Unknown result type (might be due to invalid IL or missing references) //IL_0758: Expected O, but got Unknown if ((Object)(object)_list == (Object)null || (Object)(object)_page == (Object)null || _rows == null) { return; } _avatarRefreshTargets.Clear(); bool flag = _viewingLobbyId == "__search__"; bool flag2 = _viewingLobbyId != null && !flag; bool flag3 = _viewingLobbyId != null; bool flag4 = !flag3; if ((Object)(object)_backButton != (Object)null) { ((Component)_backButton).gameObject.SetActive(flag3); try { _backButton.SetText("← " + Loc.T("ROOMHISTORY_BACK")); } catch { } } if ((Object)(object)_trashButton != (Object)null) { ((Component)_trashButton).gameObject.SetActive(flag4); try { _trashButton.SetText(_deleteMode ? Loc.T("ROOMHISTORY_DELETE_MODE_OFF") : Loc.T("ROOMHISTORY_DELETE_MODE_ON")); _trashButton.SetWidth(200f); _trashButton.SetColor(_deleteMode ? new Color(0.55f, 0.12f, 0.12f) : new Color(0.22f, 0.18f, 0.15f), true); } catch { } } if ((Object)(object)_searchButton != (Object)null) { ((Component)_searchButton).gameObject.SetActive(flag4); try { _searchButton.SetText(Loc.T("ROOMHISTORY_SEARCH")); } catch { } } if ((Object)(object)_searchInput != (Object)null) { ((Component)_searchInput).gameObject.SetActive(flag4); try { if (flag4) { _searchInput.SetPlaceholder(Loc.T("ROOMHISTORY_SEARCH_PLACEHOLDER")); } } catch { } } PeakText? titleText = _titleText; if ((Object)(object)((titleText != null) ? titleText.TextMesh : null) != (Object)null) { if (flag) { ((TMP_Text)_titleText.TextMesh).text = Loc.T("ROOMHISTORY_SEARCH_TITLE"); } else if (flag2) { ((TMP_Text)_titleText.TextMesh).text = Loc.T("ROOMHISTORY_PLAYERS_TITLE"); } else { ((TMP_Text)_titleText.TextMesh).text = Loc.T("ROOMHISTORY_PAGE_TITLE"); } HistoryUiFont.Apply(_titleText); HistoryUiFont.ForceCenteredLabel(_titleText, 900f, 50f, new Vector2(0f, -36f)); ((TMP_Text)_titleText.TextMesh).ForceMeshUpdate(true, false); } bool flag5 = _deleteMode && flag4; PeakText? hintText = _hintText; if ((Object)(object)((hintText != null) ? hintText.TextMesh : null) != (Object)null) { if (flag5) { ((TMP_Text)_hintText.TextMesh).text = ""; ((Component)_hintText).gameObject.SetActive(false); } else { ((Component)_hintText).gameObject.SetActive(true); if (flag) { ((TMP_Text)_hintText.TextMesh).text = (string.IsNullOrEmpty(_searchQuery) ? Loc.T("ROOMHISTORY_SEARCH_HINT") : ("\"" + _searchQuery + "\"")); ((Graphic)_hintText.TextMesh).color = new Color(0.85f, 0.9f, 1f); } else if (flag2) { RoomRecord room = Plugin.Store.GetRoom(_viewingLobbyId); string text = ((room == null || string.IsNullOrWhiteSpace(room.HostName)) ? Loc.T("ROOMHISTORY_UNKNOWN_HOST") : room.HostName); int num = room?.Players.Count ?? 0; string text2 = Loc.Tf("ROOMHISTORY_ROOM_TITLE", text); if (num > 0) { text2 = text2 + " · " + Loc.Tf("ROOMHISTORY_PLAYER_COUNT", num); } ((TMP_Text)_hintText.TextMesh).text = text2; ((Graphic)_hintText.TextMesh).color = new Color(0.85f, 0.9f, 1f); } else { ((TMP_Text)_hintText.TextMesh).text = Loc.T("ROOMHISTORY_HINT_JOIN_VIEW") + " · " + Loc.T("ROOMHISTORY_HINT_DOUBLECLICK"); ((Graphic)_hintText.TextMesh).color = new Color(1f, 0.92f, 0.35f); } HistoryUiFont.Apply(_hintText); HistoryUiFont.ForceCenteredLabel(_hintText, 880f, 36f, new Vector2(0f, -88f)); ((TMP_Text)_hintText.TextMesh).ForceMeshUpdate(true, false); } } PeakText? hintText2 = _hintText2; if ((Object)(object)((hintText2 != null) ? hintText2.TextMesh : null) != (Object)null) { if (flag5 || flag3) { ((TMP_Text)_hintText2.TextMesh).text = ""; ((Component)_hintText2).gameObject.SetActive(false); } else { ((Component)_hintText2).gameObject.SetActive(true); if (HistoryUi.IsTitleScene) { ((TMP_Text)_hintText2.TextMesh).text = Loc.Tf("ROOMHISTORY_HINT_HOTKEY", Plugin.ModConfig.QuickJoinHotkey.Value, Plugin.ModConfig.ModMenuHotkey.Value); } else { ((TMP_Text)_hintText2.TextMesh).text = Loc.Tf("ROOMHISTORY_HINT_HOTKEY_INGAME", Plugin.ModConfig.ModMenuHotkey.Value); } HistoryUiFont.Apply(_hintText2); HistoryUiFont.ForceCenteredLabel(_hintText2, 880f, 32f, new Vector2(0f, -118f)); ((TMP_Text)_hintText2.TextMesh).ForceMeshUpdate(true, false); } } if ((Object)(object)_deleteBanner != (Object)null) { bool flag6 = flag5; ((Component)_deleteBanner).gameObject.SetActive(flag6); if (flag6 && (Object)(object)_deleteBanner.TextMesh != (Object)null) { ((TMP_Text)_deleteBanner.TextMesh).text = Loc.T("ROOMHISTORY_DELETE_BANNER"); ((Graphic)_deleteBanner.TextMesh).color = new Color(1f, 0.35f, 0.3f); HistoryUiFont.Apply(_deleteBanner); HistoryUiFont.ForceCenteredLabel(_deleteBanner, 900f, 40f, new Vector2(0f, -88f)); ((TMP_Text)_deleteBanner.TextMesh).ForceMeshUpdate(true, false); } } RectTransform component = ((Component)_list).GetComponent(); if ((Object)(object)component != (Object)null) { component.anchoredPosition = new Vector2(0f, flag5 ? (-210f) : (-200f)); } if ((Object)(object)_list != (Object)null) { foreach (Transform item in (Transform)_list.Content) { Transform val = item; DestroyRowSafely(((Component)val).gameObject); } } if ((Object)(object)_friendList != (Object)null) { foreach (Transform item2 in (Transform)_friendList.Content) { Transform val2 = item2; DestroyRowSafely(((Component)val2).gameObject); } ((Component)_friendList).gameObject.SetActive(flag4); } if ((Object)(object)_friendPanelTitle != (Object)null) { ((Component)_friendPanelTitle).gameObject.SetActive(flag4); } if (flag) { PopulateSearchResults(_searchQuery ?? ""); return; } if (flag2) { PopulatePlayers(_viewingLobbyId); return; } PopulateFriendPanel(); PopulateRooms(); } private static void DestroyRowSafely(GameObject row) { if ((Object)(object)row == (Object)null) { return; } try { Image[] componentsInChildren = row.GetComponentsInChildren(true); foreach (Image val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.sprite == (Object)null) && ((Object)((Component)val).gameObject).name.StartsWith("Avatar", StringComparison.Ordinal)) { Sprite sprite = val.sprite; val.sprite = null; Object.Destroy((Object)(object)sprite); } } } catch { } Object.Destroy((Object)(object)row); } private void PopulateFriendPanel() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList friends = FriendRoomsService.GetFriends(); if (friends.Count == 0) { PeakText peakText = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_FRIENDS_EMPTY")).SetFontSize(22f).SetColor(Color.yellow), (Transform)(object)_friendList.Content), new Vector2(0.5f, 1f)), new Vector2(0.5f, 1f)), new Vector2(0f, -40f)); HistoryUiFont.Apply(peakText); return; } foreach (FriendRoomInfo item in friends) { _friendRows.CreateFriendRow(item); } } private void PopulateSearchResults(string query) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList readOnlyList = Plugin.Store.SearchPlayers(query); if (readOnlyList.Count == 0) { PeakText peakText = ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_SEARCH_EMPTY")).SetFontSize(28f).SetColor(Color.yellow), (Transform)(object)_list.Content); HistoryUiFont.Apply(peakText); return; } foreach (PlayerSearchHit item in readOnlyList) { _rows.CreateSearchHitRow(item); } } private void PopulateRooms() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList rooms = Plugin.Store.GetRooms(); if (rooms.Count == 0) { PeakText peakText = ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_EMPTY_ROOMS")).SetFontSize(28f).SetColor(Color.yellow), (Transform)(object)_list.Content); HistoryUiFont.Apply(peakText); return; } foreach (RoomRecord item in rooms) { _rows.CreateRoomRow(item, _deleteMode); } } private void PopulatePlayers(string lobbyId) { //IL_0038: 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_008a: 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) RoomRecord room = Plugin.Store.GetRoom(lobbyId); if (room == null || room.Players.Count == 0) { PeakText val = ElementExtensions.ParentTo(MenuAPI.CreateText(Loc.T("ROOMHISTORY_EMPTY_PLAYERS")).SetFontSize(28f).SetColor(Color.yellow), (Transform)(object)_list.Content); HistoryUiFont.Apply(val); RectTransform component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { ElementExtensions.SetAnchorMinMax(val, new Vector2(0.5f, 1f)); ElementExtensions.SetPivot(val, new Vector2(0.5f, 1f)); ElementExtensions.SetPosition(val, new Vector2(0f, -20f)); } return; } foreach (PlayerRecord item in from x in room.Players orderby x.WasHost descending, x.LastSeenUtcTicks descending select x) { _rows.CreatePlayerRow(item); } } private void OnRoomRowClicked(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 live lobby."); return; } _lastClickLobbyId = null; RoomJoiner.TryJoin(room); } } } internal sealed class HistoryRowFactory { private readonly Transform _listContent; private readonly float _listWidth; private readonly float _roomRowHeight; private readonly float _playerRowHeight; private readonly List _avatarRefreshTargets; private readonly Action _refreshView; private readonly Action _showPlayers; private readonly Action _onRoomRowClicked; public HistoryRowFactory(Transform listContent, float listWidth, float roomRowHeight, float playerRowHeight, List avatarRefreshTargets, Action refreshView, Action showPlayers, Action onRoomRowClicked) { _listContent = listContent; _listWidth = listWidth; _roomRowHeight = roomRowHeight; _playerRowHeight = playerRowHeight; _avatarRefreshTargets = avatarRefreshTargets; _refreshView = refreshView; _showPlayers = showPlayers; _onRoomRowClicked = onRoomRowClicked; } public void CreateRoomRow(RoomRecord room, bool deleteMode) { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Expected O, but got Unknown //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Expected O, but got Unknown //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Expected O, but got Unknown //IL_0384: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Expected O, but got Unknown string text = (string.IsNullOrWhiteSpace(room.HostName) ? Loc.T("ROOMHISTORY_UNKNOWN_HOST") : room.HostName); string item = Loc.Tf("ROOMHISTORY_ROOM_TITLE", text); string text2 = Loc.Tf("ROOMHISTORY_JOINED_AGO", Loc.Relative(room.JoinedAtUtc)); PeakButton val = ElementExtensions.ParentTo(MenuAPI.CreateButton("Room_" + room.LobbyId), _listContent).SetHeight(_roomRowHeight); PinRowSize(((Component)val).gameObject, _listWidth, _roomRowHeight, pinMinHeight: true); if (deleteMode) { Image componentInChildren = ((Component)val).GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { ((Graphic)componentInChildren).color = new Color(0.55f, 0.12f, 0.12f, 0.95f); } } PeakText text3 = val.Text; if ((Object)(object)((text3 != null) ? text3.TextMesh : null) != (Object)null) { ((TMP_Text)val.Text.TextMesh).text = ""; } CreateAvatarBox(((Component)val).transform, room.HostSteamId, text, new Vector2(12f, 0f), 72f); List list = new List { item, "" + text2 + "" }; if (!string.IsNullOrWhiteSpace(room.LobbyName)) { list.Add("" + Loc.Tf("ROOMHISTORY_LOBBY_NAME", room.LobbyName) + ""); } if (room.Players.Count > 0) { list.Add("" + Loc.Tf("ROOMHISTORY_PLAYER_COUNT", room.Players.Count) + ""); } if (deleteMode) { list.Add("" + Loc.T("ROOMHISTORY_DELETE_ROOM") + ""); } string body = string.Join("\n", list); CreateBodyText(((Component)val).transform, body); if (deleteMode) { val.OnClick((UnityAction)delegate { Plugin.Store.DeleteRoom(room.LobbyId); _refreshView(); }); return; } val.OnClick((UnityAction)delegate { _onRoomRowClicked(room); }); bool flag = HistoryUi.IsTitleScene || !HistoryUi.IsInLiveLobby; if (flag && !JoinGate.IsJoinAllowed(room.LobbyId, room.JoinedAtUtcTicks)) { flag = false; } if (flag) { ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_JOIN")), ((Component)val).transform), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, 22f)).SetWidth(150f).OnClick((UnityAction)delegate { RoomJoiner.TryJoin(room); }); } ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_VIEW")), ((Component)val).transform), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, flag ? (-28f) : 0f)).SetWidth(150f).OnClick((UnityAction)delegate { _showPlayers(room.LobbyId); }); } public void CreatePlayerRow(PlayerRecord player) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(player.DisplayName) ? Loc.T("ROOMHISTORY_UNKNOWN_PLAYER") : player.DisplayName); PeakButton val = ElementExtensions.ParentTo(MenuAPI.CreateButton("Player_" + player.SteamId), _listContent).SetHeight(_playerRowHeight); PinRowSize(((Component)val).gameObject, _listWidth, _playerRowHeight, pinMinHeight: false); PeakText text2 = val.Text; if ((Object)(object)((text2 != null) ? text2.TextMesh : null) != (Object)null) { ((TMP_Text)val.Text.TextMesh).text = ""; } CreateAvatarBox(((Component)val).transform, player.SteamId, text, new Vector2(12f, 0f), 72f); List list = new List(); if (player.WasHost) { list.Add("" + Loc.T("ROOMHISTORY_HOST_BADGE") + ""); } bool flag = SteamAvatarService.IsFriend(player.SteamId); if (flag) { list.Add("" + Loc.T("ROOMHISTORY_ALREADY_FRIEND") + ""); } string text3 = text + ((list.Count > 0) ? (" " + string.Join(" ", list)) : ""); string text4 = "" + Loc.Tf("ROOMHISTORY_MET_AGO", Loc.Relative(player.LastSeenUtc)) + ""; CreateBodyText(((Component)val).transform, text3 + "\n" + text4); AddPlayerActionButtons(((Component)val).transform, player.SteamId, flag); } public void CreateSearchHitRow(PlayerSearchHit hit) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(hit.DisplayName) ? Loc.T("ROOMHISTORY_UNKNOWN_PLAYER") : hit.DisplayName); PeakButton val = ElementExtensions.ParentTo(MenuAPI.CreateButton("Search_" + hit.SteamId), _listContent).SetHeight(_playerRowHeight); PinRowSize(((Component)val).gameObject, _listWidth, _playerRowHeight, pinMinHeight: false); PeakText text2 = val.Text; if ((Object)(object)((text2 != null) ? text2.TextMesh : null) != (Object)null) { ((TMP_Text)val.Text.TextMesh).text = ""; } CreateAvatarBox(((Component)val).transform, hit.SteamId, text, new Vector2(12f, 0f), 72f); List list = new List(); if (hit.WasHost) { list.Add("" + Loc.T("ROOMHISTORY_HOST_BADGE") + ""); } bool flag = SteamAvatarService.IsFriend(hit.SteamId); if (flag) { list.Add("" + Loc.T("ROOMHISTORY_ALREADY_FRIEND") + ""); } string text3 = text + ((list.Count > 0) ? (" " + string.Join(" ", list)) : ""); string text4 = "" + Loc.Tf("ROOMHISTORY_MET_AGO", Loc.Relative(hit.LastSeenUtc)) + " " + Loc.Tf("ROOMHISTORY_MET_IN_ROOM", hit.RoomHostName) + ""; CreateBodyText(((Component)val).transform, text3 + "\n" + text4); AddPlayerActionButtons(((Component)val).transform, hit.SteamId, flag); } private void AddPlayerActionButtons(Transform row, string steamId, bool friend) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0103: 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_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown //IL_0196: Unknown result type (might be due to invalid IL or missing references) string text = SteamAvatarService.LocalSteamIdString(); if (text != null && string.Equals(text, steamId, StringComparison.Ordinal)) { return; } string sid = steamId; bool canShowInvite = HistoryUi.CanShowInvite; if (canShowInvite) { PeakMenuButton inviteBtn = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_INVITE")), row), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, 22f)).SetWidth(150f); inviteBtn.OnClick((UnityAction)delegate { //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (!SteamInviteService.InvitePlayer(sid)) { return; } try { if ((Object)(object)inviteBtn.Button != (Object)null) { ((Selectable)inviteBtn.Button).interactable = false; } if ((Object)(object)((PeakLocalizableElement)inviteBtn).Text != (Object)null) { ((Graphic)((PeakLocalizableElement)inviteBtn).Text).color = new Color(0.6f, 0.6f, 0.6f, 1f); } inviteBtn.SetText(Loc.T("ROOMHISTORY_INVITED")); } catch { } }); } string text2 = (friend ? Loc.T("ROOMHISTORY_ALREADY_FRIEND") : Loc.T("ROOMHISTORY_ADD_FRIEND")); PeakMenuButton val = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(text2), row), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, canShowInvite ? (-28f) : 0f)).SetWidth(150f); if (!friend) { val.OnClick((UnityAction)delegate { SteamAvatarService.OpenAddFriend(sid); }); return; } try { if ((Object)(object)val.Button != (Object)null) { ((Selectable)val.Button).interactable = false; } if ((Object)(object)((PeakLocalizableElement)val).Text != (Object)null) { ((Graphic)((PeakLocalizableElement)val).Text).color = new Color(0.6f, 0.6f, 0.6f, 1f); } } catch { } } public void CreateFriendRow(FriendRoomInfo friend) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: 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_0300: Expected O, but got Unknown //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(friend.Name) ? Loc.T("ROOMHISTORY_UNKNOWN_PLAYER") : friend.Name); PeakButton val = ElementExtensions.ParentTo(MenuAPI.CreateButton("Friend_" + friend.SteamId), _listContent).SetHeight(_playerRowHeight); PinRowSize(((Component)val).gameObject, _listWidth, _playerRowHeight, pinMinHeight: false); PeakText text2 = val.Text; if ((Object)(object)((text2 != null) ? text2.TextMesh : null) != (Object)null) { ((TMP_Text)val.Text.TextMesh).text = ""; } CreateAvatarBox(((Component)val).transform, friend.SteamId.ToString(), text, new Vector2(12f, 0f), 72f); FriendGameMode mode = friend.Mode; string text3 = mode switch { FriendGameMode.Joinable => "" + Loc.T("ROOMHISTORY_FRIEND_IN_GAME") + "", FriendGameMode.Solo => "" + Loc.T("ROOMHISTORY_FRIEND_SOLO") + "", FriendGameMode.InviteOnly => "" + Loc.T("ROOMHISTORY_FRIEND_INVITE_ONLY") + "", FriendGameMode.Unknown => "" + Loc.T("ROOMHISTORY_FRIEND_UNKNOWN") + "", _ => "" + Loc.T("ROOMHISTORY_FRIEND_MAIN_MENU") + "", }; string text4 = text; string text5 = "" + text3 + ""; CreateBodyText(((Component)val).transform, text4 + "\n" + text5); bool flag = mode == FriendGameMode.Joinable && (HistoryUi.IsTitleScene || !HistoryUi.IsInLiveLobby); bool flag2 = mode == FriendGameMode.MainMenu && HistoryUi.IsInLiveLobby; if (flag) { string lobbyId = friend.LobbyId.ToString(); ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_JOIN")), ((Component)val).transform), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, 0f)).SetWidth(150f).OnClick((UnityAction)delegate { RoomJoiner.TryJoinLobbyId(lobbyId, hidePage: true); }); return; } if (flag2) { string sid = friend.SteamId.ToString(); PeakMenuButton inviteBtn = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(UiButtonFactory.CreateMenuButtonSafe(Loc.T("ROOMHISTORY_INVITE")), ((Component)val).transform), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, 0f)).SetWidth(150f); inviteBtn.OnClick((UnityAction)delegate { //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (!SteamInviteService.InvitePlayer(sid)) { return; } try { if ((Object)(object)inviteBtn.Button != (Object)null) { ((Selectable)inviteBtn.Button).interactable = false; } if ((Object)(object)((PeakLocalizableElement)inviteBtn).Text != (Object)null) { ((Graphic)((PeakLocalizableElement)inviteBtn).Text).color = new Color(0.6f, 0.6f, 0.6f, 1f); } inviteBtn.SetText(Loc.T("ROOMHISTORY_INVITED")); } catch { } }); return; } string text6 = mode switch { FriendGameMode.MainMenu => Loc.T("ROOMHISTORY_FRIEND_MAIN_MENU"), FriendGameMode.Unknown => Loc.T("ROOMHISTORY_FRIEND_UNKNOWN"), _ => Loc.T("ROOMHISTORY_FRIEND_IN_GAME"), }; try { PeakText val2 = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(text6), ((Component)val).transform), new Vector2(1f, 0.5f)), new Vector2(1f, 0.5f)), new Vector2(-16f, 0f)).SetFontSize(20f); HistoryUiFont.Apply(val2); if ((Object)(object)val2.TextMesh != (Object)null) { ((Graphic)val2.TextMesh).color = new Color(0.6f, 0.6f, 0.6f, 1f); } } catch { } } private void CreateBodyText(Transform parent, string body) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) PeakText val = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(body), parent), new Vector2(0f, 0.5f)), new Vector2(0f, 0.5f)), new Vector2(96f, 0f)).SetFontSize(24f); HistoryUiFont.Apply(val); if ((Object)(object)val.TextMesh != (Object)null) { ((Graphic)val.TextMesh).color = Color.white; ((TMP_Text)val.TextMesh).richText = true; ((Graphic)val.TextMesh).raycastTarget = false; ((TMP_Text)val.TextMesh).text = body; ((TMP_Text)val.TextMesh).ForceMeshUpdate(true, false); } } private void CreateAvatarBox(Transform parent, string steamId, string displayName, Vector2 pos, float size) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: 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) GameObject val = new GameObject(string.IsNullOrEmpty(steamId) ? "AvatarPlaceholder" : ("Avatar_" + steamId), new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0f, 0.5f); component.anchorMax = val2; component.anchorMin = val2; component.pivot = new Vector2(0f, 0.5f); component.anchoredPosition = pos; component.sizeDelta = new Vector2(size, size); Image component2 = val.GetComponent(); ((Graphic)component2).color = new Color(0.18f, 0.14f, 0.11f, 1f); ((Graphic)component2).raycastTarget = false; Texture2D val3 = null; if (!string.IsNullOrEmpty(steamId)) { val3 = SteamAvatarService.GetAvatar(steamId); if ((Object)(object)val3 == (Object)null) { _avatarRefreshTargets.Add(val); } } if ((Object)(object)val3 != (Object)null) { component2.sprite = Sprite.Create(val3, new Rect(0f, 0f, (float)((Texture)val3).width, (float)((Texture)val3).height), new Vector2(0.5f, 0.5f)); ((Graphic)component2).color = Color.white; component2.preserveAspect = true; return; } string text = "?"; if (!string.IsNullOrWhiteSpace(displayName)) { text = displayName.Trim().Substring(0, 1); } PeakText val4 = ElementExtensions.SetPosition(ElementExtensions.SetPivot(ElementExtensions.SetAnchorMinMax(ElementExtensions.ParentTo(MenuAPI.CreateText(text), val.transform), new Vector2(0.5f, 0.5f)), new Vector2(0.5f, 0.5f)), Vector2.zero).SetFontSize(size * 0.4f); HistoryUiFont.Apply(val4); if ((Object)(object)val4.TextMesh != (Object)null) { ((TMP_Text)val4.TextMesh).alignment = (TextAlignmentOptions)514; ((Graphic)val4.TextMesh).color = Color.white; ((TMP_Text)val4.TextMesh).text = text; } } private static void PinRowSize(GameObject go, float width, float height, bool pinMinHeight) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) RectTransform component = go.GetComponent(); if ((Object)(object)component != (Object)null) { component.sizeDelta = new Vector2(width, height); } LayoutElement val = go.GetComponent() ?? go.AddComponent(); val.preferredWidth = width; val.minWidth = width; val.preferredHeight = height; if (pinMinHeight) { val.minHeight = height; } } } public sealed class HistoryStore { private const int FormatVersion = 3; private const char Sep = '\t'; 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 _rooms = new List(); private bool _dirty; private float _lastDirtyUnscaledTime; public string StoragePath => _filePath; public HistoryStore(string configPath, PluginConfig config) { _config = config; string text = Path.Combine(configPath, "RoomHub"); Directory.CreateDirectory(text); _filePath = Path.Combine(text, "history.tsv"); _legacyReTracePath = Path.Combine(configPath, "ReTrace", "history.tsv"); _legacyQuickRejoinPath = Path.Combine(configPath, "QuickRejoin", "recent_rooms.tsv"); } public IReadOnlyList GetRooms() { lock (_lock) { PruneUnlocked(saveIfChanged: true); return _rooms.OrderByDescending((RoomRecord r) => r.JoinedAtUtcTicks).Take(Math.Max(1, _config.MaxRecentRooms.Value)).Select(CloneRoom) .ToList(); } } public RoomRecord? GetMostRecent() { IReadOnlyList 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 SearchPlayers(string query) { string text = (query ?? "").Trim(); if (text.Length == 0) { return Array.Empty(); } lock (_lock) { PruneUnlocked(saveIfChanged: true); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (RoomRecord room in _rooms) { string roomHostName = (string.IsNullOrWhiteSpace(room.HostName) ? room.LobbyId : room.HostName); foreach (PlayerRecord player in room.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 = room.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; long num = 0L; lock (_lock) { RoomRecord roomRecord = _rooms.FirstOrDefault((RoomRecord r) => string.Equals(r.LobbyId, lobbyId, StringComparison.Ordinal)); if (roomRecord != null) { num = roomRecord.JoinedAtUtcTicks; roomRecord.JoinedAtUtcTicks = ticks; if (!string.IsNullOrWhiteSpace(hostSteamId)) { roomRecord.HostSteamId = hostSteamId.Trim(); } if (!string.IsNullOrWhiteSpace(hostName)) { roomRecord.HostName = Sanitize(hostName); } if (!string.IsNullOrWhiteSpace(lobbyName)) { roomRecord.LobbyName = Sanitize(lobbyName); } _rooms.Remove(roomRecord); _rooms.Insert(0, roomRecord); } else { _rooms.Insert(0, new RoomRecord { LobbyId = lobbyId.Trim(), HostSteamId = (hostSteamId ?? "").Trim(), HostName = Sanitize(hostName), LobbyName = Sanitize(lobbyName), JoinedAtUtcTicks = ticks }); } PruneUnlocked(saveIfChanged: false); SaveUnlocked(); } if (num > 0) { long num2 = (ticks - num) / 600000000; Plugin.Log.LogInfo((object)("Room saved (rejoin): lobby=" + lobbyId + ", host='" + hostName + "', " + $"joinedAt refreshed (+{num2} min since previous) → {_filePath}")); } else { Plugin.Log.LogInfo((object)("Room saved (new): lobby=" + lobbyId + ", host='" + hostName + "' → " + _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; _rooms.Remove(roomRecord); _rooms.Insert(0, roomRecord); 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 (wasHost) { ApplySoleHostUnlocked(roomRecord, sid, 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) { foreach (PlayerRecord player in room.Players) { player.WasHost = string.Equals(player.SteamId, hostSteamId, StringComparison.Ordinal); } room.HostSteamId = hostSteamId; if (!string.IsNullOrWhiteSpace(hostDisplayName)) { 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 void TickDeferredSave(float unscaledTime) { lock (_lock) { if (_dirty && !(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(); try { if (File.Exists(_filePath)) { LoadFileUnlocked(_filePath); } else if (File.Exists(_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)) { LoadFileUnlocked(_legacyQuickRejoinPath); if (_rooms.Count > 0) { SaveUnlocked(); } Plugin.Log.LogInfo((object)$"Migrated {_rooms.Count} room(s) from Quick Rejoin TSV."); } PruneUnlocked(saveIfChanged: true); Plugin.Log.LogInfo((object)$"Loaded {_rooms.Count} room(s) from {_filePath}"); } catch (Exception arg) { Plugin.Log.LogError((object)$"History load failed: {arg}"); _rooms = new List(); } } } public void Flush() { lock (_lock) { 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 dictionary = new Dictionary(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) && result > 0) { 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)) { if (!long.TryParse(array2[6], out var 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) && result4 > 0) { 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 }; } } } foreach (RoomRecord value3 in dictionary.Values) { value3.HostName = Sanitize(value3.HostName); value3.LobbyName = Sanitize(value3.LobbyName); value3.HostSteamId = (value3.HostSteamId ?? "").Trim(); if (!string.IsNullOrWhiteSpace(value3.HostSteamId)) { ApplySoleHostUnlocked(value3, value3.HostSteamId, value3.HostName); continue; } List 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 void PruneUnlocked(bool saveIfChanged) { int count = Math.Max(1, _config.MaxRecentRooms.Value); int num = Math.Max(1, _config.HistoryExpireHours.Value); long cutoff = DateTime.UtcNow.AddHours(-num).Ticks; int count2 = _rooms.Count; _rooms = (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(); if (saveIfChanged && _rooms.Count != count2) { SaveUnlocked(); } } private void SaveUnlocked() { try { string directoryName = Path.GetDirectoryName(_filePath); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } 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'); } } string text = _filePath + ".tmp"; 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; } catch (Exception arg) { Plugin.Log.LogError((object)$"History save failed: {arg}"); } } 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; 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 { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) try { SteamLobbyHandler service = GameHandler.GetService(); CSteamID val = default(CSteamID); return service != null && service.InSteamLobby(ref val) && val != CSteamID.Nil; } catch { return false; } } } public static bool CanShowInvite { get { if (!IsTitleScene) { return IsInLiveLobby; } return false; } } public static void Open() { if (!Plugin.ModConfig.Enabled.Value) { RoomJoiner.ShowFeedback(Loc.T("ROOMHISTORY_DISABLED"), Loc.T("ROOMHISTORY_DISABLED")); return; } try { PauseMenuHook.ClosePauseMenuIfOpen(); UiButtonFactory.EnsureTemplate(); 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() { try { _page?.Discard(); } catch { } _page = null; } public static void OnSceneLoaded() { DropPage(); } public static void Tick() { if (_page == null) { return; } if (!_page.HasLivePage) { DropPage(); } else if (_page.IsPageOpen) { _page.TickInputBlock(); if (Input.GetKeyDown((KeyCode)27) && !IsTextInputFocused()) { _page.HandleBackOrClose(); } else { _page.TickAvatarRefresh(); } } } public static bool IsTextInputFocused() { try { EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null) { return false; } GameObject currentSelectedGameObject = current.currentSelectedGameObject; if ((Object)(object)currentSelectedGameObject == (Object)null) { return false; } if ((Object)(object)currentSelectedGameObject.GetComponent() != (Object)null) { return true; } if ((Object)(object)currentSelectedGameObject.GetComponent() != (Object)null) { return true; } Transform parent = currentSelectedGameObject.transform.parent; int num = 0; while (num < 4 && (Object)(object)parent != (Object)null) { if ((Object)(object)((Component)parent).GetComponent() != (Object)null || (Object)(object)((Component)parent).GetComponent() != (Object)null) { return true; } num++; parent = parent.parent; } } catch { } return false; } private static void EnsureController() { if (_page != null && !_page.HasLivePage) { DropPage(); } if (_page == null) { _page = new HistoryPageController(); } } } internal static class HistoryUiFont { private static TMP_FontAsset? _uiFont; private static Material? _uiFontMat; public static void Reset() { _uiFont = null; _uiFontMat = null; } public static void CaptureFromMenuButton() { if ((Object)(object)_uiFont != (Object)null) { return; } try { if (TryCaptureFromScene()) { return; } PeakMenuButton val = UiButtonFactory.CreateMenuButtonSafe("字"); try { TextMeshProUGUI componentInChildren = ((Component)val).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)((TMP_Text)componentInChildren).font != (Object)null) { _uiFont = ((TMP_Text)componentInChildren).font; _uiFontMat = ((TMP_Text)componentInChildren).fontSharedMaterial; Plugin.Log.LogInfo((object)("UI font from PeakMenuButton: " + ((Object)_uiFont).name + " mat=" + (((Object)(object)_uiFontMat != (Object)null) ? ((Object)_uiFontMat).name : "null"))); } } finally { Object.Destroy((Object)(object)((Component)val).gameObject); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("CaptureUiFont: " + ex.Message)); } } private static bool TryCaptureFromScene() { try { TextMeshProUGUI[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (TextMeshProUGUI val in array) { if (!((Object)(object)((val != null) ? ((TMP_Text)val).font : null) == (Object)null) && !string.IsNullOrEmpty(((TMP_Text)val).text) && ((Component)val).gameObject.activeInHierarchy && (ContainsCjk(((TMP_Text)val).text) || ((Object)val).name.IndexOf("Button", StringComparison.OrdinalIgnoreCase) >= 0 || (Object)(object)((Component)val).GetComponentInParent() != (Object)null)) { _uiFont = ((TMP_Text)val).font; _uiFontMat = ((TMP_Text)val).fontSharedMaterial; Plugin.Log.LogInfo((object)("UI font from scene " + ((Object)val).name + ": " + ((Object)_uiFont).name)); return true; } } } catch { } return false; } public static void Apply(PeakText? peakText) { if (!((Object)(object)((peakText != null) ? peakText.TextMesh : null) == (Object)null) && !((Object)(object)_uiFont == (Object)null)) { ((TMP_Text)peakText.TextMesh).font = _uiFont; if ((Object)(object)_uiFontMat != (Object)null) { ((TMP_Text)peakText.TextMesh).fontSharedMaterial = _uiFontMat; } } } public static void ForceCenteredLabel(PeakText? peakText, float width, float height, Vector2 anchoredPos) { //IL_002e: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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) if ((Object)(object)peakText == (Object)null) { return; } RectTransform component = ((Component)peakText).GetComponent(); if (!((Object)(object)component == (Object)null)) { Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0.5f, 1f); component.anchorMax = val; component.anchorMin = val; component.pivot = new Vector2(0.5f, 1f); component.anchoredPosition = anchoredPos; component.sizeDelta = new Vector2(width, height); if ((Object)(object)peakText.TextMesh != (Object)null) { ((TMP_Text)peakText.TextMesh).alignment = (TextAlignmentOptions)514; ((TMP_Text)peakText.TextMesh).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)peakText.TextMesh).overflowMode = (TextOverflowModes)1; ((TMP_Text)peakText.TextMesh).enableAutoSizing = true; ((TMP_Text)peakText.TextMesh).fontSizeMin = 16f; ((TMP_Text)peakText.TextMesh).fontSizeMax = ((((TMP_Text)peakText.TextMesh).fontSize > 0f) ? ((TMP_Text)peakText.TextMesh).fontSize : 44f); } } } private static bool ContainsCjk(string text) { foreach (char c in text) { if (c >= '㐀' && c <= '鿿') { return true; } } return false; } } internal static class JoinGate { private sealed class Entry { public float CooldownUntil; } public const double MaxLobbyAgeHours = 2.5; private const float JoinFailCooldownSec = 600f; private static readonly Dictionary _cooldowns = new Dictionary(); public static event Action? StateChanged; public static bool IsJoinAllowed(string lobbyId, long joinedAtUtcTicks) { if (joinedAtUtcTicks > 0) { DateTime dateTime = new DateTime(joinedAtUtcTicks, DateTimeKind.Utc); if ((DateTime.UtcNow - dateTime).TotalHours > 2.5) { return false; } } if (ulong.TryParse(lobbyId, out var result) && result != 0L && _cooldowns.TryGetValue(result, out Entry value) && Time.unscaledTime < value.CooldownUntil) { return false; } return true; } public static void MarkJoinFailed(ulong lobbyId) { if (lobbyId == 0L) { return; } _cooldowns[lobbyId] = new Entry { CooldownUntil = Time.unscaledTime + 600f }; try { JoinGate.StateChanged?.Invoke(); } catch { } } } internal static class LobbyJoinFeedback { private static Callback? _callback; private static bool _inited; private static float _lastShownUnscaled; private const float MinShowIntervalSec = 2f; public static void Init() { if (_inited) { return; } try { _callback = Callback.Create((DispatchDelegate)OnLobbyEnter); _inited = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("LobbyJoinFeedback init failed: " + ex.Message)); } } private static void OnLobbyEnter(LobbyEnter_t param) { //IL_0000: 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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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) EChatRoomEnterResponse val = (EChatRoomEnterResponse)param.m_EChatRoomEnterResponse; if ((int)val != 1) { float unscaledTime = Time.unscaledTime; if (!(unscaledTime - _lastShownUnscaled < 2f)) { _lastShownUnscaled = unscaledTime; string text = Describe(val); Plugin.Log.LogInfo((object)($"Join lobby failed lobby={param.m_ulSteamIDLobby} " + $"code={(int)param.m_EChatRoomEnterResponse} ({text})")); JoinGate.MarkJoinFailed(param.m_ulSteamIDLobby); RoomJoiner.ShowFeedback(Loc.T("ROOMHISTORY_JOIN_FAILED_TITLE"), text); } } } private static string Describe(EChatRoomEnterResponse code) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected I4, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected I4, but got Unknown return (code - 2) switch { 0 => Loc.T("ROOMHISTORY_JOIN_FAIL_DOESNT_EXIST"), 1 => Loc.T("ROOMHISTORY_JOIN_FAIL_NOT_ALLOWED"), 2 => Loc.T("ROOMHISTORY_JOIN_FAIL_FULL"), 4 => Loc.T("ROOMHISTORY_JOIN_FAIL_BANNED"), _ => Loc.Tf("ROOMHISTORY_JOIN_FAIL_OTHER", (int)code), }; } } [HarmonyPatch(typeof(SteamLobbyHandler), "OnLobbyEnter")] internal static class SteamLobbyEnterPatch { private static void Postfix(SteamLobbyHandler __instance, LobbyEnter_t param) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.ModConfig.Enabled.Value || param.m_EChatRoomEnterResponse != 1) { return; } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(param.m_ulSteamIDLobby); if (val == CSteamID.Nil || param.m_ulSteamIDLobby == 0L) { return; } CSteamID steamID = SteamUser.GetSteamID(); CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(val); bool flag = lobbyOwner == steamID; string hostSteamId = lobbyOwner.m_SteamID.ToString(); string lobbyId = val.m_SteamID.ToString(); if (flag && !Plugin.ModConfig.RecordWhenHosting.Value) { Plugin.Log.LogDebug((object)"Skip room list entry (hosting)."); PlayerTracker.SetActiveLobby(lobbyId, hostSteamId); return; } string text = SteamMatchmaking.GetLobbyData(val, "host_Name"); if (string.IsNullOrWhiteSpace(text)) { try { text = SteamFriends.GetFriendPersonaName(lobbyOwner); } catch { text = ""; } } if (string.IsNullOrWhiteSpace(text)) { text = Loc.T("ROOMHISTORY_UNKNOWN_HOST"); } string lobbyName = SteamMatchmaking.GetLobbyData(val, "lobby_Name") ?? ""; Plugin.Store.UpsertRoom(lobbyId, hostSteamId, text, lobbyName); PlayerTracker.SetActiveLobby(lobbyId, hostSteamId); SnapshotMembers(val, lobbyOwner); Plugin.Store.SetCurrentHost(lobbyId, hostSteamId, text); } catch (Exception arg) { Plugin.Log.LogError((object)$"Lobby record failed: {arg}"); } } private static void SnapshotMembers(CSteamID lobbyId, CSteamID owner) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) try { int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(lobbyId); CSteamID steamID = SteamUser.GetSteamID(); string lobbyId2 = lobbyId.m_SteamID.ToString(); for (int i = 0; i < numLobbyMembers; i++) { CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(lobbyId, i); if (!(lobbyMemberByIndex == CSteamID.Nil) && !(lobbyMemberByIndex == steamID)) { string text; try { text = SteamFriends.GetFriendPersonaName(lobbyMemberByIndex); } catch { text = ""; } if (string.IsNullOrWhiteSpace(text)) { text = Loc.T("ROOMHISTORY_UNKNOWN_PLAYER"); } bool wasHost = lobbyMemberByIndex == owner; Plugin.Store.UpsertPlayer(lobbyId2, lobbyMemberByIndex.m_SteamID.ToString(), text, wasHost); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("SnapshotMembers: " + ex.Message)); } } } public static class Loc { public const string MainButton = "ROOMHISTORY_MAIN_BUTTON"; public const string PageTitle = "ROOMHISTORY_PAGE_TITLE"; public const string PlayersTitle = "ROOMHISTORY_PLAYERS_TITLE"; public const string Close = "ROOMHISTORY_CLOSE"; public const string Back = "ROOMHISTORY_BACK"; public const string Join = "ROOMHISTORY_JOIN"; public const string View = "ROOMHISTORY_VIEW"; public const string AddFriend = "ROOMHISTORY_ADD_FRIEND"; public const string Invite = "ROOMHISTORY_INVITE"; public const string Invited = "ROOMHISTORY_INVITED"; public const string InviteFailed = "ROOMHISTORY_INVITE_FAILED"; public const string InviteInvalid = "ROOMHISTORY_INVITE_INVALID"; public const string InviteNeedLobby = "ROOMHISTORY_INVITE_NEED_LOBBY"; public const string EmptyRooms = "ROOMHISTORY_EMPTY_ROOMS"; public const string EmptyPlayers = "ROOMHISTORY_EMPTY_PLAYERS"; public const string RoomTitle = "ROOMHISTORY_ROOM_TITLE"; public const string JoinedAgo = "ROOMHISTORY_JOINED_AGO"; public const string LobbyName = "ROOMHISTORY_LOBBY_NAME"; public const string NoRecent = "ROOMHISTORY_NO_RECENT"; public const string InvalidId = "ROOMHISTORY_INVALID_ID"; public const string Disabled = "ROOMHISTORY_DISABLED"; public const string HintHotkey = "ROOMHISTORY_HINT_HOTKEY"; public const string HintHotkeyInGame = "ROOMHISTORY_HINT_HOTKEY_INGAME"; public const string HintJoinView = "ROOMHISTORY_HINT_JOIN_VIEW"; public const string HintDoubleClick = "ROOMHISTORY_HINT_DOUBLECLICK"; public const string DeleteModeOn = "ROOMHISTORY_DELETE_MODE_ON"; public const string DeleteModeOff = "ROOMHISTORY_DELETE_MODE_OFF"; public const string DeleteBanner = "ROOMHISTORY_DELETE_BANNER"; public const string DeleteRoom = "ROOMHISTORY_DELETE_ROOM"; public const string AlreadyFriend = "ROOMHISTORY_ALREADY_FRIEND"; public const string HostBadge = "ROOMHISTORY_HOST_BADGE"; public const string MetAgo = "ROOMHISTORY_MET_AGO"; public const string RelJustNow = "ROOMHISTORY_REL_JUST_NOW"; public const string RelMinutes = "ROOMHISTORY_REL_MINUTES"; public const string RelHours = "ROOMHISTORY_REL_HOURS"; public const string RelDays = "ROOMHISTORY_REL_DAYS"; public const string UnknownHost = "ROOMHISTORY_UNKNOWN_HOST"; public const string UnknownPlayer = "ROOMHISTORY_UNKNOWN_PLAYER"; public const string PlayerCount = "ROOMHISTORY_PLAYER_COUNT"; public const string Search = "ROOMHISTORY_SEARCH"; public const string SearchPlaceholder = "ROOMHISTORY_SEARCH_PLACEHOLDER"; public const string SearchTitle = "ROOMHISTORY_SEARCH_TITLE"; public const string SearchEmpty = "ROOMHISTORY_SEARCH_EMPTY"; public const string SearchHint = "ROOMHISTORY_SEARCH_HINT"; public const string MetInRoom = "ROOMHISTORY_MET_IN_ROOM"; public const string FriendsTitle = "ROOMHISTORY_FRIENDS_TITLE"; public const string FriendInGame = "ROOMHISTORY_FRIEND_IN_GAME"; public const string FriendInviteOnly = "ROOMHISTORY_FRIEND_INVITE_ONLY"; public const string FriendSolo = "ROOMHISTORY_FRIEND_SOLO"; public const string FriendMainMenu = "ROOMHISTORY_FRIEND_MAIN_MENU"; public const string FriendUnknown = "ROOMHISTORY_FRIEND_UNKNOWN"; public const string FriendsEmpty = "ROOMHISTORY_FRIENDS_EMPTY"; public const string JoinFailedTitle = "ROOMHISTORY_JOIN_FAILED_TITLE"; public const string JoinFailFull = "ROOMHISTORY_JOIN_FAIL_FULL"; public const string JoinFailDoesntExist = "ROOMHISTORY_JOIN_FAIL_DOESNT_EXIST"; public const string JoinFailNotAllowed = "ROOMHISTORY_JOIN_FAIL_NOT_ALLOWED"; public const string JoinFailBanned = "ROOMHISTORY_JOIN_FAIL_BANNED"; public const string JoinFailOther = "ROOMHISTORY_JOIN_FAIL_OTHER"; public static void Register() { L("ROOMHISTORY_MAIN_BUTTON", "Room Hub", "房间中心", "房間中心"); L("ROOMHISTORY_PAGE_TITLE", "Room Hub", "房间中心", "房間中心"); L("ROOMHISTORY_PLAYERS_TITLE", "Players met", "遇见的玩家", "遇見的玩家"); L("ROOMHISTORY_CLOSE", "Close", "关闭", "關閉"); L("ROOMHISTORY_BACK", "Back", "返回", "返回"); L("ROOMHISTORY_JOIN", "Join", "加入", "加入"); L("ROOMHISTORY_VIEW", "View", "查看", "查看"); L("ROOMHISTORY_ADD_FRIEND", "Add Friend", "加好友", "加好友"); L("ROOMHISTORY_INVITE", "Invite", "邀请", "邀請"); L("ROOMHISTORY_INVITED", "Invited", "已邀请", "已邀請"); L("ROOMHISTORY_INVITE_FAILED", "Invite failed.", "邀请失败。", "邀請失敗。"); L("ROOMHISTORY_INVITE_INVALID", "Invalid Steam ID.", "无效的 Steam ID。", "無效的 Steam ID。"); L("ROOMHISTORY_INVITE_NEED_LOBBY", "You must be in a multiplayer lobby to invite.", "需要先处于多人房间中才能邀请。", "需要先處於多人房間中才能邀請。"); L("ROOMHISTORY_EMPTY_ROOMS", "No rooms yet.\nJoin a multiplayer lobby and it will appear here.", "暂无房间记录。\n加入多人房间后会出现在这里。", "暫無房間記錄。\n加入多人房間後會出現在這裡。"); L("ROOMHISTORY_EMPTY_PLAYERS", "No other players recorded for this room yet.", "该房间还没有记录到其他玩家。", "該房間還沒有記錄到其他玩家。"); L("ROOMHISTORY_ROOM_TITLE", "{0}'s Room", "{0}的房间", "{0}的房間"); L("ROOMHISTORY_JOINED_AGO", "Joined {0}", "{0}前加入", "{0}前加入"); L("ROOMHISTORY_LOBBY_NAME", "Name: {0}", "名称:{0}", "名稱:{0}"); L("ROOMHISTORY_NO_RECENT", "No room in history to rejoin.", "暂无最近房间可加入。", "暫無最近房間可加入。"); L("ROOMHISTORY_INVALID_ID", "Invalid lobby ID in history.", "历史中的房间 ID 无效。", "歷史中的房間 ID 無效。"); L("ROOMHISTORY_DISABLED", "Room Hub is disabled in config.", "房间中心已在配置中关闭。", "房間中心已在設定中關閉。"); L("ROOMHISTORY_HINT_HOTKEY", "Quick join [{0}] · Mod menu [{1}] on the title screen", "主界面按 [{0}] 快速加入最近房间 · 按 [{1}] 打开房间中心", "主介面按 [{0}] 快速加入最近房間 · 按 [{1}] 打開房間中心"); L("ROOMHISTORY_HINT_HOTKEY_INGAME", "Mod menu hotkey [{0}] opens Room Hub · pause menu also has a button", "按 [{0}] 打开房间中心 · 暂停菜单也有入口", "按 [{0}] 打開房間中心 · 暫停選單也有入口"); L("ROOMHISTORY_HINT_JOIN_VIEW", "Use Join / View on each room", "使用每个房间右侧的「加入 / 查看」", "使用每個房間右側的「加入 / 查看」"); L("ROOMHISTORY_HINT_DOUBLECLICK", "Double-click a room to join", "双击房间也可加入", "雙擊房間也可加入"); L("ROOMHISTORY_DELETE_MODE_ON", "Delete Mode", "删除模式", "刪除模式"); L("ROOMHISTORY_DELETE_MODE_OFF", "Done", "完成", "完成"); L("ROOMHISTORY_DELETE_BANNER", "Delete Mode — Click a red room to remove it", "删除模式 — 点击红色房间可从历史中删除", "刪除模式 — 點擊紅色房間可從歷史中刪除"); L("ROOMHISTORY_DELETE_ROOM", "Delete", "删除", "刪除"); L("ROOMHISTORY_ALREADY_FRIEND", "Friends", "已是好友", "已是好友"); L("ROOMHISTORY_HOST_BADGE", "Host", "房主", "房主"); L("ROOMHISTORY_MET_AGO", "Met {0}", "{0}前遇见", "{0}前遇見"); L("ROOMHISTORY_REL_JUST_NOW", "just now", "刚刚", "剛剛"); L("ROOMHISTORY_REL_MINUTES", "{0}m", "{0} 分钟", "{0} 分鐘"); L("ROOMHISTORY_REL_HOURS", "{0}h", "{0} 小时", "{0} 小時"); L("ROOMHISTORY_REL_DAYS", "{0}d", "{0} 天", "{0} 天"); L("ROOMHISTORY_UNKNOWN_HOST", "Unknown", "未知房主", "未知房主"); L("ROOMHISTORY_UNKNOWN_PLAYER", "Unknown player", "未知玩家", "未知玩家"); L("ROOMHISTORY_PLAYER_COUNT", "{0} players met", "遇见 {0} 人", "遇見 {0} 人"); L("ROOMHISTORY_SEARCH", "Search", "搜索", "搜尋"); L("ROOMHISTORY_SEARCH_PLACEHOLDER", "Player name…", "输入玩家名…", "輸入玩家名…"); L("ROOMHISTORY_SEARCH_TITLE", "Search players", "搜索玩家", "搜尋玩家"); L("ROOMHISTORY_SEARCH_EMPTY", "No players matched your search.", "没有找到匹配的玩家。", "沒有找到匹配的玩家。"); L("ROOMHISTORY_SEARCH_HINT", "Search across everyone you've met", "在遇见过的玩家中搜索", "在遇見過的玩家中搜尋"); L("ROOMHISTORY_MET_IN_ROOM", "In: {0}", "房间:{0}", "房間:{0}"); L("ROOMHISTORY_FRIENDS_TITLE", "Steam Friends", "steam好友", "Steam好友"); L("ROOMHISTORY_FRIEND_IN_GAME", "In game", "在游戏中", "在遊戲中"); L("ROOMHISTORY_FRIEND_INVITE_ONLY", "Invite only", "仅邀请", "僅邀請"); L("ROOMHISTORY_FRIEND_SOLO", "Single player", "单人游戏中", "單人遊戲中"); L("ROOMHISTORY_FRIEND_MAIN_MENU", "In main menu", "在主菜单中", "在主選單中"); L("ROOMHISTORY_FRIEND_UNKNOWN", "Status unknown", "状态未知", "狀態未知"); L("ROOMHISTORY_FRIENDS_EMPTY", "No friends in a game right now.", "暂无好友在游戏中。", "暫無好友在遊戲中。"); L("ROOMHISTORY_JOIN_FAILED_TITLE", "Cannot join", "无法加入", "無法加入"); L("ROOMHISTORY_JOIN_FAIL_FULL", "The room is full.", "房间已满。", "房間已滿。"); L("ROOMHISTORY_JOIN_FAIL_DOESNT_EXIST", "The room no longer exists.", "房间不存在。", "房間不存在。"); L("ROOMHISTORY_JOIN_FAIL_NOT_ALLOWED", "You are not allowed to join this room.", "没有权限加入该房间。", "沒有權限加入該房間。"); L("ROOMHISTORY_JOIN_FAIL_BANNED", "You are banned from this room.", "你已被该房间封禁。", "你已被該房間封禁。"); L("ROOMHISTORY_JOIN_FAIL_OTHER", "Join failed (code {0}).", "加入失败(错误码 {0})。", "加入失敗(錯誤碼 {0})。"); } private static void L(string index, string en, string zhCn, string zhTw) { MenuAPI.CreateLocalization(index).AddLocalization(en, (Language)0).AddLocalization(zhCn, (Language)9) .AddLocalization(zhTw, (Language)10); } public static string T(string index) { try { string text = LocalizedText.GetText(index, false); if (string.IsNullOrEmpty(text) || text.StartsWith("MISSING")) { return index; } return text; } catch { return index; } } public static string Tf(string index, params object[] args) { try { if (args != null) { for (int i = 0; i < args.Length; i++) { if (args[i] is string text && (text.IndexOf('{') >= 0 || text.IndexOf('}') >= 0)) { args[i] = text.Replace("{", "{{").Replace("}", "}}"); } } } return string.Format(T(index), args); } catch { return T(index); } } public static string Relative(DateTime utc) { TimeSpan timeSpan = DateTime.UtcNow - utc.ToUniversalTime(); if (timeSpan.TotalMinutes < 1.0) { return T("ROOMHISTORY_REL_JUST_NOW"); } if (timeSpan.TotalHours < 1.0) { return Tf("ROOMHISTORY_REL_MINUTES", Math.Max(1, (int)timeSpan.TotalMinutes)); } if (timeSpan.TotalDays < 1.0) { return Tf("ROOMHISTORY_REL_HOURS", Math.Max(1, (int)timeSpan.TotalHours)); } return Tf("ROOMHISTORY_REL_DAYS", Math.Max(1, (int)timeSpan.TotalDays)); } } internal static class LocalizationSync { private static readonly HashSet Handlers = new HashSet(); private static bool _hooked; public static void Register(Action handler) { if (handler == null) { return; } lock (Handlers) { Handlers.Add(handler); if (!_hooked) { try { LocalizedText.OnLangugageChanged = (Action)Delegate.Combine(LocalizedText.OnLangugageChanged, new Action(OnLanguageChanged)); _hooked = true; return; } catch (Exception ex) { Plugin.Log.LogWarning((object)("LocalizationSync hook failed: " + ex.Message)); return; } } } } public static void Unregister(Action handler) { if (handler == null) { return; } lock (Handlers) { Handlers.Remove(handler); if (_hooked && Handlers.Count == 0) { try { LocalizedText.OnLangugageChanged = (Action)Delegate.Remove(LocalizedText.OnLangugageChanged, new Action(OnLanguageChanged)); } catch { } _hooked = false; } } } private static void OnLanguageChanged() { Action[] array; lock (Handlers) { array = new Action[Handlers.Count]; Handlers.CopyTo(array); } Action[] array2 = array; foreach (Action action in array2) { try { action(); } catch { } } } } public sealed class RoomRecord { public string LobbyId = ""; public string HostSteamId = ""; public string HostName = ""; public string LobbyName = ""; public long JoinedAtUtcTicks; public readonly List Players = new List(); public DateTime JoinedAtUtc { get { if (JoinedAtUtcTicks > 0) { return new DateTime(JoinedAtUtcTicks, DateTimeKind.Utc); } return DateTime.MinValue; } set { JoinedAtUtcTicks = value.ToUniversalTime().Ticks; } } } public sealed class PlayerRecord { public string SteamId = ""; public string DisplayName = ""; public bool WasHost; public long FirstSeenUtcTicks; public long LastSeenUtcTicks; public DateTime FirstSeenUtc { get { if (FirstSeenUtcTicks > 0) { return new DateTime(FirstSeenUtcTicks, DateTimeKind.Utc); } return DateTime.MinValue; } set { FirstSeenUtcTicks = value.ToUniversalTime().Ticks; } } public DateTime LastSeenUtc { get { if (LastSeenUtcTicks > 0) { return new DateTime(LastSeenUtcTicks, DateTimeKind.Utc); } return DateTime.MinValue; } set { LastSeenUtcTicks = value.ToUniversalTime().Ticks; } } } public sealed class PlayerSearchHit { public string SteamId = ""; public string DisplayName = ""; public bool WasHost; public long LastSeenUtcTicks; public string LobbyId = ""; public string RoomHostName = ""; public DateTime LastSeenUtc { get { if (LastSeenUtcTicks > 0) { return new DateTime(LastSeenUtcTicks, DateTimeKind.Utc); } return DateTime.MinValue; } } } internal static class PauseMenuHook { [CompilerGenerated] private static class <>O { public static BuilderDelegate <0>__BuildPauseButton; public static Action <1>__RefreshLabel; public static UnityAction <2>__OnPauseRoomHubClicked; } private const string ButtonObjectName = "UI_Pause_RoomHub"; private static readonly Color ButtonPink = new Color(0.92f, 0.42f, 0.68f, 1f); private static readonly FieldInfo? QuitButtonField = typeof(PauseMenuMainPage).GetField("m_quitButton", BindingFlags.Instance | BindingFlags.NonPublic); private static bool _registered; public static void Register() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown if (_registered) { return; } _registered = true; try { object obj = <>O.<0>__BuildPauseButton; if (obj == null) { BuilderDelegate val = BuildPauseButton; <>O.<0>__BuildPauseButton = val; obj = (object)val; } MenuAPI.AddToPauseMenu((BuilderDelegate)obj); LocalizationSync.Register(RefreshLabel); Plugin.Log.LogInfo((object)"Pause menu Room History button registered."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Pause menu register failed: " + ex.Message)); } } public static void Unregister() { LocalizationSync.Unregister(RefreshLabel); _registered = false; } private static void RefreshLabel() { try { PeakMenuButton[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (PeakMenuButton val in array) { if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)null && ((Object)val).name == "UI_Pause_RoomHub") { val.SetText(Loc.T("ROOMHISTORY_MAIN_BUTTON")); break; } } } catch { } } private static void BuildPauseButton(Transform parent) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)parent == (Object)null) { return; } try { for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if ((Object)(object)child != (Object)null && ((Object)child).name == "UI_Pause_RoomHub") { return; } } PeakMenuButton obj = ElementExtensions.ParentTo(UiButtonFactory.CreatePauseMenuButtonSafe(Loc.T("ROOMHISTORY_MAIN_BUTTON")), parent); object obj2 = <>O.<2>__OnPauseRoomHubClicked; if (obj2 == null) { UnityAction val = OnPauseRoomHubClicked; <>O.<2>__OnPauseRoomHubClicked = val; obj2 = (object)val; } PeakMenuButton val2 = obj.OnClick((UnityAction)obj2); GameObject gameObject = ((Component)val2).gameObject; ((Object)gameObject).name = "UI_Pause_RoomHub"; try { val2.SetColor(ButtonPink, true); } catch (Exception ex) { Plugin.Log.LogDebug((object)("Pause button style: " + ex.Message)); } PlaceUnderQuitButton(parent, gameObject.transform); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Pause menu button build failed: " + ex2.Message)); } } private static void PlaceUnderQuitButton(Transform pageRoot, Transform ourButton) { //IL_00e8: 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_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0186: 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_019b: Unknown result type (might be due to invalid IL or missing references) try { PauseMenuMainPage val = ((Component)pageRoot).GetComponent() ?? ((Component)pageRoot).GetComponentInParent(); if ((Object)(object)val == (Object)null || QuitButtonField == null) { ourButton.SetAsLastSibling(); Plugin.Log.LogWarning((object)"PauseMenuMainPage.m_quitButton not found; button at end of list."); return; } object? value = QuitButtonField.GetValue(val); Button val2 = (Button)((value is Button) ? value : null); if ((Object)(object)val2 == (Object)null) { ourButton.SetAsLastSibling(); return; } Transform transform = ((Component)val2).transform; Transform val3 = (Transform)(((Object)(object)transform.parent != (Object)null) ? ((object)transform.parent) : ((object)pageRoot)); ourButton.SetParent(val3, false); ourButton.SetSiblingIndex(transform.GetSiblingIndex() + 1); if (!((Object)(object)((Component)val3).GetComponent() != (Object)null)) { RectTransform component = ((Component)val2).GetComponent(); RectTransform val4 = (RectTransform)(((object)((ourButton is RectTransform) ? ourButton : null)) ?? ((object)((Component)ourButton).GetComponent())); if ((Object)(object)component != (Object)null && (Object)(object)val4 != (Object)null) { val4.anchorMin = component.anchorMin; val4.anchorMax = component.anchorMax; val4.pivot = component.pivot; val4.sizeDelta = component.sizeDelta; ((Transform)val4).localScale = ((Transform)component).localScale; ((Transform)val4).localRotation = ((Transform)component).localRotation; float num = 8f; Rect rect = component.rect; float num2 = Mathf.Abs(((Rect)(ref rect)).height); if (num2 < 1f) { num2 = Mathf.Abs(component.sizeDelta.y); } if (num2 < 1f) { num2 = 56f; } val4.anchoredPosition = component.anchoredPosition + new Vector2(0f, 0f - (num2 + num)); } } RectTransform val5 = (RectTransform)(object)((val3 is RectTransform) ? val3 : null); if (val5 != null) { LayoutRebuilder.ForceRebuildLayoutImmediate(val5); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("PlaceUnderQuitButton: " + ex.Message)); try { ourButton.SetAsLastSibling(); } catch { } } } private static void OnPauseRoomHubClicked() { HistoryUi.Open(); } public static void ClosePauseMenuIfOpen() { try { PauseMenuHandler val = Object.FindFirstObjectByType(); if (!((Object)(object)val == (Object)null)) { GameObject gameObject = ((Component)val).gameObject; if (gameObject.activeInHierarchy) { gameObject.SetActive(false); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("ClosePauseMenuIfOpen: " + ex.Message)); } } } public static class PlayerTracker { private static string? _activeLobbyId; private static string? _activeHostSteamId; private static Callback? _lobbyChatUpdate; private static bool _callbacksInited; private static float _lastHostPollUnscaled; private static float _lastChatSyncUnscaled; public static void SetActiveLobby(string lobbyId, string hostSteamId) { _activeLobbyId = lobbyId; _activeHostSteamId = hostSteamId; EnsureCallbacks(); } public static void ClearActiveLobby() { _activeLobbyId = null; _activeHostSteamId = null; } public static void Shutdown() { try { _lobbyChatUpdate?.Dispose(); } catch { } _lobbyChatUpdate = null; _callbacksInited = false; ClearActiveLobby(); } private static void EnsureCallbacks() { if (_callbacksInited) { return; } try { _lobbyChatUpdate = Callback.Create((DispatchDelegate)OnLobbyChatUpdate); _callbacksInited = true; } catch (Exception ex) { Plugin.Log.LogDebug((object)("LobbyChatUpdate callback init failed: " + ex.Message)); } } private static void OnLobbyChatUpdate(LobbyChatUpdate_t param) { try { if (Plugin.ModConfig.Enabled.Value && !string.IsNullOrEmpty(_activeLobbyId) && string.Equals(_activeLobbyId, param.m_ulSteamIDLobby.ToString(), StringComparison.Ordinal)) { float unscaledTime = Time.unscaledTime; if (!(unscaledTime - _lastChatSyncUnscaled < 2f)) { _lastChatSyncUnscaled = unscaledTime; SyncHostFromSteam(); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("OnLobbyChatUpdate: " + ex.Message)); } } public static void TickHostPoll(float unscaledTime) { if (!string.IsNullOrEmpty(_activeLobbyId) && !(unscaledTime - _lastHostPollUnscaled < 5f)) { _lastHostPollUnscaled = unscaledTime; SyncHostFromSteam(); } } public static string? EnsureActiveLobbyFromSteam() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { SteamLobbyHandler service = GameHandler.GetService(); CSteamID val = default(CSteamID); if (service == null || !service.InSteamLobby(ref val) || val == CSteamID.Nil) { return _activeLobbyId; } CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(val); string text = val.m_SteamID.ToString(); string hostSteamId = ((lobbyOwner != CSteamID.Nil) ? lobbyOwner.m_SteamID.ToString() : (_activeHostSteamId ?? "")); SetActiveLobby(text, hostSteamId); return text; } catch { return _activeLobbyId; } } public static void SyncHostFromSteam() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_002d: 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) try { SteamLobbyHandler service = GameHandler.GetService(); CSteamID val = default(CSteamID); if (service == null || !service.InSteamLobby(ref val) || val == CSteamID.Nil) { return; } CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(val); if (!(lobbyOwner == CSteamID.Nil)) { string text = val.m_SteamID.ToString(); string text2 = lobbyOwner.m_SteamID.ToString(); string hostDisplayName = ""; try { hostDisplayName = SteamFriends.GetFriendPersonaName(lobbyOwner); } catch { } if (string.Equals(_activeLobbyId, text, StringComparison.Ordinal) && string.Equals(_activeHostSteamId, text2, StringComparison.Ordinal)) { SetActiveLobby(text, text2); return; } SetActiveLobby(text, text2); Plugin.Store.SetCurrentHost(text, text2, hostDisplayName); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("SyncHostFromSteam: " + ex.Message)); } } internal static void RecordPhotonPlayer(Player? player, bool syncHost = true) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.ModConfig.Enabled.Value || player == null || player.IsLocal) { return; } if (syncHost) { SyncHostFromSteam(); } string text = _activeLobbyId; if (string.IsNullOrEmpty(text)) { text = EnsureActiveLobbyFromSteam(); } if (string.IsNullOrEmpty(text)) { return; } string text2 = player.UserId ?? ""; if (string.IsNullOrWhiteSpace(text2) || text2 == "0") { return; } string text3 = SteamAvatarService.LocalSteamIdString(); if (text3 != null && string.Equals(text3, text2, StringComparison.Ordinal)) { return; } string text4 = player.NickName ?? ""; if (string.IsNullOrWhiteSpace(text4) && ulong.TryParse(text2, out var result)) { try { text4 = SteamFriends.GetFriendPersonaName(new CSteamID(result)); } catch { text4 = ""; } } if (string.IsNullOrWhiteSpace(text4) || text4 == "Bing Bong") { text4 = Loc.T("ROOMHISTORY_UNKNOWN_PLAYER"); } bool wasHost = !string.IsNullOrEmpty(_activeHostSteamId) && string.Equals(_activeHostSteamId, text2, StringComparison.Ordinal); Plugin.Store.UpsertPlayer(text, text2, text4, wasHost); } catch (Exception ex) { Plugin.Log.LogWarning((object)("RecordPhotonPlayer: " + ex.Message)); } } } [HarmonyPatch(typeof(PlayerConnectionLog), "OnPlayerEnteredRoom")] internal static class OnPlayerEnteredRoomPatch { private static void Postfix(Player newPlayer) { if (newPlayer != null && !newPlayer.IsLocal) { PlayerTracker.RecordPhotonPlayer(newPlayer); } } } [HarmonyPatch(typeof(NetworkConnector), "OnJoinedRoom")] internal static class OnJoinedRoomPatch { private static void Postfix() { try { if (!Plugin.ModConfig.Enabled.Value) { return; } string text = PlayerTracker.EnsureActiveLobbyFromSteam(); if (!string.IsNullOrEmpty(text)) { Plugin.Store.TouchRoomJoinedNow(text, "Photon.OnJoinedRoom"); } if (PhotonNetwork.PlayerList != null) { Player[] playerList = PhotonNetwork.PlayerList; foreach (Player player in playerList) { PlayerTracker.RecordPhotonPlayer(player, syncHost: false); } } PlayerTracker.SyncHostFromSteam(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("OnJoinedRoom snapshot: " + ex.Message)); } } } [HarmonyPatch(typeof(NetworkConnector), "OnLeftRoom")] internal static class OnLeftRoomPatch { private static void Postfix() { PlayerTracker.ClearActiveLobby(); } } [HarmonyPatch(typeof(SteamLobbyHandler), "OnLobbyDataUpdate")] internal static class OnLobbyDataUpdateHostPatch { private static void Postfix() { try { if (Plugin.ModConfig.Enabled.Value) { PlayerTracker.SyncHostFromSteam(); } } catch { } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.roomhub.RoomHub", "Room Hub", "2.6.13")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.roomhub.RoomHub"; public const string PluginName = "Room Hub"; public const string PluginVersion = "2.6.13"; private Harmony? _harmony; private TitleMenuController? _titleUi; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static PluginConfig ModConfig { get; private set; } internal static HistoryStore Store { get; private set; } private void Awake() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig = new PluginConfig(((BaseUnityPlugin)this).Config); Store = new HistoryStore(Paths.ConfigPath, ModConfig); Store.Load(); Loc.Register(); SteamAvatarService.Init(); FriendRoomsService.Init(); LobbyJoinFeedback.Init(); PauseMenuHook.Register(); _harmony = new Harmony("com.roomhub.RoomHub"); _harmony.PatchAll(typeof(Plugin).Assembly); SceneManager.sceneLoaded += OnSceneLoaded; Log.LogInfo((object)("Room Hub v2.6.13 loaded. History=" + Store.StoragePath)); } private void OnDestroy() { try { Store?.Flush(); } catch { } SceneManager.sceneLoaded -= OnSceneLoaded; HistoryUi.DropPage(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } SteamAvatarService.Shutdown(); PlayerTracker.Shutdown(); PauseMenuHook.Unregister(); } private void OnApplicationQuit() { try { Store?.Flush(); } catch { } } private void Update() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: 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_0081: 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_0091: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) HistoryUi.Tick(); try { Store?.TickDeferredSave(Time.unscaledTime); } catch { } PlayerTracker.TickHostPoll(Time.unscaledTime); if (!ModConfig.Enabled.Value || !ModConfig.EnableHotkey.Value || HistoryUi.IsTextInputFocused()) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (!((Scene)(ref activeScene)).IsValid()) { return; } bool flag = string.Equals(((Scene)(ref activeScene)).name, "Title", StringComparison.Ordinal); KeyCode value = ModConfig.QuickJoinHotkey.Value; KeyCode value2 = ModConfig.ModMenuHotkey.Value; if (flag) { if ((int)value != 0 && Input.GetKeyDown(value)) { RoomJoiner.TryJoinMostRecent(showEmptyFeedback: true); } if ((int)value2 != 0 && Input.GetKeyDown(value2)) { HistoryUi.Toggle(); } return; } try { if (LoadingScreenHandler.loading) { return; } } catch { } if ((int)value2 != 0 && Input.GetKeyDown(value2)) { HistoryUi.Toggle(); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { HistoryUi.OnSceneLoaded(); if (!(((Scene)(ref scene)).name != "Title") && ModConfig.Enabled.Value && ModConfig.ShowMainMenuButton.Value) { if ((Object)(object)_titleUi == (Object)null) { _titleUi = ((Component)this).gameObject.GetComponent() ?? ((Component)this).gameObject.AddComponent(); } _titleUi.OnTitleSceneLoaded(); } } } public sealed class PluginConfig { private readonly ConfigFile _file; public const int MaxPlayersPerRoomFixed = 20; public ConfigEntry Enabled { get; } public ConfigEntry ShowMainMenuButton { get; } public ConfigEntry EnableHotkey { get; } public ConfigEntry QuickJoinHotkey { get; } public ConfigEntry ModMenuHotkey { get; } public ConfigEntry ButtonPosX { get; } public ConfigEntry ButtonPosY { get; } public ConfigEntry UseCustomButtonPos { get; } public ConfigEntry MaxRecentRooms { get; } public ConfigEntry HistoryExpireHours { get; } public ConfigEntry RecordWhenHosting { get; } public PluginConfig(ConfigFile config) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected O, but got Unknown _file = config; Enabled = config.Bind("General", "Enable Mod (启用模组)", true, "Master switch.\n总开关。"); ShowMainMenuButton = config.Bind("UI", "Show Main Menu Button (显示主菜单按钮)", true, "Show Room Hub button on the title screen.\n主界面显示「房间中心」按钮。"); UseCustomButtonPos = config.Bind("UI", "Use Custom Button Position (使用自定义按钮位置)", false, "When true, Button Pos X/Y are applied. Also set automatically after right-click drag on the title button.\n为 true 时使用下方 XY。在主界面右键拖动按钮后会自动开启。"); ButtonPosX = config.Bind("UI", "Button Pos X (按钮X坐标)", 0f, new ConfigDescription("Anchored X relative to the right-middle of MainPage (negative = inward). Right-click-drag also saves this.\n相对主界面右中锚点的 X(负值向内)。也可右键拖动按钮保存。", (AcceptableValueBase)(object)new AcceptableValueRange(-2000f, 2000f), Array.Empty())); ButtonPosY = config.Bind("UI", "Button Pos Y (按钮Y坐标)", 0f, new ConfigDescription("Anchored Y relative to the right-middle of MainPage. Right-click-drag also saves this.\n相对主界面右中锚点的 Y。也可右键拖动按钮保存。", (AcceptableValueBase)(object)new AcceptableValueRange(-2000f, 2000f), Array.Empty())); EnableHotkey = config.Bind("Hotkey", "Enable Hotkey (启用快捷键)", true, "Master switch for both hotkeys below.\n下方两个快捷键的总开关(不是第三个按键)。"); QuickJoinHotkey = config.Bind("Hotkey", "快速加入快捷键 (Quick Join Hotkey)", (KeyCode)106, "Title screen: rejoin most recent room. Default J.\n主界面:按 J 快速加入最近房间。默认 J。"); ModMenuHotkey = config.Bind("Hotkey", "模组菜单快捷键 (Mod Menu Hotkey)", (KeyCode)107, "Title screen and in-game: open/close Room Hub panel. Default K.\n主界面与局内均可:打开/关闭房间中心面板。默认 K。"); MaxRecentRooms = config.Bind("History", "Max Recent Rooms (最多房间数)", 10, new ConfigDescription("Max rooms kept. When over limit, oldest rooms are removed first.\n最多保留房间数。超出时优先删除最旧的房间。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), Array.Empty())); HistoryExpireHours = config.Bind("History", "History Expire Hours (过期小时数)", 168, new ConfigDescription("Auto-remove rooms older than this many hours. Default 168 = 7 days.\n超过该小时数自动清除。默认 168 = 7 天。", (AcceptableValueBase)(object)new AcceptableValueRange(1, 720), Array.Empty())); RecordWhenHosting = config.Bind("History", "Record When Hosting (主持时也记录房间)", false, "Also record a room entry when you host.\n自己开房是否记入历史。"); } public void SaveButtonPosition(float x, float y) { ButtonPosX.Value = x; ButtonPosY.Value = y; UseCustomButtonPos.Value = true; try { _file.Save(); } catch { } } } public sealed class RibbonDragHandle : MonoBehaviour, IPointerDownHandler, IEventSystemHandler, IDragHandler, IPointerUpHandler { private RectTransform? _rect; private RectTransform? _parent; private Canvas? _canvas; private bool _dragging; private Vector2 _grabOffset; public void Init(RectTransform rect, RectTransform parent) { _rect = rect; _parent = parent; _canvas = ((Component)rect).GetComponentInParent(); } public void OnPointerDown(PointerEventData eventData) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0030: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if ((int)eventData.button == 1 && !((Object)(object)_rect == (Object)null) && !((Object)(object)_parent == (Object)null)) { _dragging = true; if (ScreenToParentLocal(eventData.position, out var local)) { _grabOffset = _rect.anchoredPosition - local; } } } public void OnDrag(PointerEventData eventData) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (_dragging && (int)eventData.button == 1 && !((Object)(object)_rect == (Object)null) && ScreenToParentLocal(eventData.position, out var local)) { Vector2 val = local + _grabOffset; val.x = Mathf.Clamp(val.x, -1800f, 200f); val.y = Mathf.Clamp(val.y, -900f, 900f); _rect.anchoredPosition = val; } } public void OnPointerUp(PointerEventData eventData) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (_dragging && (int)eventData.button == 1) { _dragging = false; if (!((Object)(object)_rect == (Object)null)) { Vector2 anchoredPosition = _rect.anchoredPosition; Plugin.ModConfig.SaveButtonPosition(anchoredPosition.x, anchoredPosition.y); Plugin.Log.LogInfo((object)$"Room History button position saved: ({anchoredPosition.x:F1}, {anchoredPosition.y:F1})"); } } } private bool ScreenToParentLocal(Vector2 screen, out Vector2 local) { //IL_0001: 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_002d: Unknown result type (might be due to invalid IL or missing references) local = default(Vector2); if ((Object)(object)_parent == (Object)null) { return false; } Camera val = null; if ((Object)(object)_canvas != (Object)null && (int)_canvas.renderMode != 0) { val = _canvas.worldCamera; } return RectTransformUtility.ScreenPointToLocalPointInRectangle(_parent, screen, val, ref local); } } public static class RoomJoiner { public static void TryJoin(RoomRecord room, bool hidePage = true) { if (room == null) { ShowNoRecentFeedback(); } else { TryJoinLobbyId(room.LobbyId, hidePage); } } public static void TryJoinMostRecent(bool showEmptyFeedback) { if (!Plugin.ModConfig.Enabled.Value) { if (showEmptyFeedback) { ShowModal(Loc.T("ROOMHISTORY_DISABLED"), ""); } return; } RoomRecord mostRecent = Plugin.Store.GetMostRecent(); if (mostRecent == null) { if (showEmptyFeedback) { ShowNoRecentFeedback(); } } else { TryJoinLobbyId(mostRecent.LobbyId, hidePage: true); } } private static void ShowNoRecentFeedback() { ShowModal(Loc.T("ROOMHISTORY_NO_RECENT"), ""); } public static void TryJoinLobbyId(string lobbyId, bool hidePage) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ModConfig.Enabled.Value) { return; } if (!HistoryUi.IsTitleScene && HistoryUi.IsInLiveLobby) { Plugin.Log.LogDebug((object)"TryJoinLobby skipped: already in live lobby."); return; } if (string.IsNullOrWhiteSpace(lobbyId) || !ulong.TryParse(lobbyId.Trim(), out var result) || result == 0L) { ShowModal(Loc.T("ROOMHISTORY_INVALID_ID"), ""); return; } try { if (hidePage) { HistoryUi.Hide(); } Plugin.Log.LogInfo((object)$"TryJoinLobby: {result}"); SteamLobbyHandler service = GameHandler.GetService(); if (service == null) { ShowModal(Loc.T("ROOMHISTORY_PAGE_TITLE"), Loc.T("ROOMHISTORY_DISABLED")); } else { service.TryJoinLobby(new CSteamID(result)); } } catch (Exception ex) { Plugin.Log.LogError((object)$"TryJoinLobby threw: {ex}"); ShowModal(Loc.T("ROOMHISTORY_PAGE_TITLE"), ex.Message); } } private static void ShowModal(string title, string body) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0039: Expected O, but got Unknown try { string text; try { text = LocalizedText.GetText("OK", true); } catch { text = "OK"; } Modal.OpenModal((HeaderModalOption)new DefaultHeaderModalOption(title, body), (ModalContentOption)new ModalButtonsOption((Option[])(object)new Option[1] { new Option(text, (Action)null) }), (Action)null); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Modal failed: " + ex.Message + ". " + title + ": " + body)); Debug.LogWarning((object)("[RoomHub] " + title + ": " + body)); } } internal static void ShowFeedback(string title, string body) { ShowModal(title, body); } } public static class SteamAvatarService { private static readonly Dictionary Cache = new Dictionary(); private static Callback? _avatarLoaded; private static bool _inited; public static void Init() { if (_inited) { return; } try { _avatarLoaded = Callback.Create((DispatchDelegate)OnAvatarLoaded); _inited = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("SteamAvatarService init failed: " + ex.Message)); } } public static void Shutdown() { try { _avatarLoaded?.Dispose(); } catch { } _avatarLoaded = null; _inited = false; foreach (KeyValuePair item in Cache) { if ((Object)(object)item.Value != (Object)null) { Object.Destroy((Object)(object)item.Value); } } Cache.Clear(); } public static Texture2D? GetAvatar(string steamIdStr) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!ulong.TryParse(steamIdStr, out var result) || result == 0L) { return null; } return GetAvatar(new CSteamID(result)); } public static Texture2D? GetAvatar(CSteamID steamId) { //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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (steamId == CSteamID.Nil || steamId.m_SteamID == 0L) { return null; } if (Cache.TryGetValue(steamId.m_SteamID, out Texture2D value) && (Object)(object)value != (Object)null) { return value; } try { SteamFriends.RequestUserInformation(steamId, true); int mediumFriendAvatar = SteamFriends.GetMediumFriendAvatar(steamId); if (mediumFriendAvatar > 0) { Texture2D val = LoadImage(mediumFriendAvatar); if ((Object)(object)val != (Object)null) { if (Cache.TryGetValue(steamId.m_SteamID, out Texture2D value2) && (Object)(object)value2 != (Object)null && (Object)(object)value2 != (Object)(object)val) { Object.Destroy((Object)(object)value2); } Cache[steamId.m_SteamID] = val; return val; } } } catch (Exception ex) { Plugin.Log.LogDebug((object)$"GetAvatar failed for {steamId}: {ex.Message}"); } return null; } private static void OnAvatarLoaded(AvatarImageLoaded_t cb) { //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_000c: Unknown result type (might be due to invalid IL or missing references) try { ulong steamID = cb.m_steamID.m_SteamID; Texture2D val = LoadImage(cb.m_iImage); if (!((Object)(object)val == (Object)null)) { if (Cache.TryGetValue(steamID, out Texture2D value) && (Object)(object)value != (Object)null && (Object)(object)value != (Object)(object)val) { Object.Destroy((Object)(object)value); } Cache[steamID] = val; } } catch (Exception ex) { Plugin.Log.LogDebug((object)("OnAvatarLoaded error: " + ex.Message)); } } private static Texture2D? LoadImage(int imageId) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown if (imageId <= 0) { return null; } uint num = default(uint); uint num2 = default(uint); if (!SteamUtils.GetImageSize(imageId, ref num, ref num2) || num == 0 || num2 == 0) { return null; } byte[] array = new byte[num * num2 * 4]; if (!SteamUtils.GetImageRGBA(imageId, array, array.Length)) { return null; } byte[] array2 = new byte[array.Length]; int num3 = (int)(num * 4); for (int i = 0; i < num2; i++) { int srcOffset = i * num3; int dstOffset = ((int)(num2 - 1) - i) * num3; Buffer.BlockCopy(array, srcOffset, array2, dstOffset, num3); } Texture2D val = new Texture2D((int)num, (int)num2, (TextureFormat)4, false); val.LoadRawTextureData(array2); val.Apply(false, true); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; return val; } public static bool IsFriend(string steamIdStr) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (!ulong.TryParse(steamIdStr, out var result) || result == 0L) { return false; } try { return SteamFriends.HasFriend(new CSteamID(result), (EFriendFlags)4); } catch { return false; } } public static void OpenAddFriend(string steamIdStr) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!ulong.TryParse(steamIdStr, out var result) || result == 0L) { return; } try { SteamFriends.ActivateGameOverlayToUser("friendadd", new CSteamID(result)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("OpenAddFriend failed: " + ex.Message)); } } public static string? LocalSteamIdString() { //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) try { return SteamUser.GetSteamID().m_SteamID.ToString(); } catch { return null; } } } public static class SteamInviteService { public static bool InvitePlayer(string steamIdStr) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (!ulong.TryParse(steamIdStr, out var result) || result == 0L) { RoomJoiner.ShowFeedback(Loc.T("ROOMHISTORY_INVITE"), Loc.T("ROOMHISTORY_INVITE_INVALID")); return false; } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(result); try { SteamLobbyHandler service = GameHandler.GetService(); CSteamID val2 = default(CSteamID); if (service != null && service.InSteamLobby(ref val2) && val2 != CSteamID.Nil) { if (SteamMatchmaking.InviteUserToLobby(val2, val)) { Plugin.Log.LogInfo((object)$"InviteUserToLobby ok → {steamIdStr} lobby={val2.m_SteamID}"); return true; } Plugin.Log.LogWarning((object)("InviteUserToLobby returned false for " + steamIdStr)); RoomJoiner.ShowFeedback(Loc.T("ROOMHISTORY_INVITE"), Loc.T("ROOMHISTORY_INVITE_FAILED")); return false; } RoomJoiner.ShowFeedback(Loc.T("ROOMHISTORY_INVITE"), Loc.T("ROOMHISTORY_INVITE_NEED_LOBBY")); return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Invite failed: " + ex.Message)); RoomJoiner.ShowFeedback(Loc.T("ROOMHISTORY_INVITE"), Loc.T("ROOMHISTORY_INVITE_FAILED")); return false; } } } public sealed class TitleMenuController : MonoBehaviour { private const string ButtonObjectName = "Button_RoomHub"; private const string ButtonsContainerPath = "MainMenu/Canvas/MainPage/Menu/Buttons"; private const string MainPagePath = "MainMenu/Canvas/MainPage"; private const string TemplateButtonPath = "MainMenu/Canvas/MainPage/Menu/Buttons/Button_PlaySolo"; private const string NextLevelPath = "MainMenu/Canvas/MainPage/NextLevelUI"; private const float MinRightEdgeInset = 28f; private const float ScreenMarginPx = 16f; private const float TemplateReadyTimeoutSec = 8f; private const float PostReadySettleSec = 0.35f; private Coroutine? _spawnRoutine; private TextMeshProUGUI? _ribbonText; private void OnDestroy() { LocalizationSync.Unregister(RefreshRibbonLabel); } public void OnTitleSceneLoaded() { HistoryUiFont.Reset(); if (_spawnRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_spawnRoutine); } _spawnRoutine = ((MonoBehaviour)this).StartCoroutine(CreateButtonWhenReady()); } private IEnumerator CreateButtonWhenReady() { GameObject val = GameObject.Find("Button_RoomHub"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } float deadline = Time.unscaledTime + 8f; while (Time.unscaledTime < deadline) { GameObject val2 = GameObject.Find("MainMenu/Canvas/MainPage/Menu/Buttons"); GameObject val3 = GameObject.Find("MainMenu/Canvas/MainPage"); GameObject val4 = GameObject.Find("MainMenu/Canvas/MainPage/Menu/Buttons/Button_PlaySolo"); if ((Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null && (Object)(object)val4 != (Object)null && val4.activeInHierarchy) { break; } yield return null; } GameObject val5 = GameObject.Find("MainMenu/Canvas/MainPage/Menu/Buttons/Button_PlaySolo"); if ((Object)(object)val5 == (Object)null || !val5.activeInHierarchy) { Plugin.Log.LogWarning((object)"Title ribbon: PlaySolo template not ready in time."); yield break; } UiButtonFactory.CaptureTemplate(val5); yield return (object)new WaitForSeconds(0.35f); yield return null; if (!Plugin.ModConfig.Enabled.Value || !Plugin.ModConfig.ShowMainMenuButton.Value || (Object)(object)GameObject.Find("Button_RoomHub") != (Object)null) { yield break; } try { SpawnMainMenuButton(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Create button failed: {arg}"); } } private void SpawnMainMenuButton() { GameObject val = GameObject.Find("MainMenu/Canvas/MainPage/Menu/Buttons/Button_PlaySolo"); GameObject val2 = GameObject.Find("MainMenu/Canvas/MainPage"); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { Plugin.Log.LogError((object)"Missing PlaySolo or MainPage."); return; } GameObject val3 = Object.Instantiate(val, val2.transform, false); ((Object)val3).name = "Button_RoomHub"; val3.SetActive(true); val3.transform.SetAsLastSibling(); Animator component = val3.GetComponent(); if ((Object)(object)component != (Object)null) { component.Play("Normal", 0, 1f); component.Update(0f); ((Behaviour)component).enabled = false; } ((MonoBehaviour)this).StartCoroutine(FinalizeButton(val3, val2)); } private IEnumerator FinalizeButton(GameObject buttonObject, GameObject mainPage) { yield return (object)new WaitForEndOfFrame(); yield return null; if ((Object)(object)buttonObject == (Object)null) { yield break; } RectTransform buttonRect = buttonObject.GetComponent(); RectTransform component = mainPage.GetComponent(); if ((Object)(object)buttonRect == (Object)null || (Object)(object)component == (Object)null) { yield break; } ((Transform)buttonRect).SetParent(mainPage.transform, false); ((Transform)buttonRect).localRotation = Quaternion.identity; ((Transform)buttonRect).localScale = new Vector3(1f, 1.3f, 1f); buttonRect.anchorMin = new Vector2(1f, 0.5f); buttonRect.anchorMax = new Vector2(1f, 0.5f); buttonRect.pivot = new Vector2(1f, 0.5f); ApplyRibbonPosition(buttonRect, component); TextMeshProUGUI componentInChildren = buttonObject.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { LocalizedText component2 = ((Component)componentInChildren).GetComponent(); if ((Object)(object)component2 != (Object)null) { Object.Destroy((Object)(object)component2); } ((TMP_Text)componentInChildren).transform.localScale = new Vector3(0.9f, 0.7f, 0.9f); _ribbonText = componentInChildren; RefreshRibbonLabel(); LocalizationSync.Register(RefreshRibbonLabel); if (((Graphic)componentInChildren).color.a < 0.2f) { ((Graphic)componentInChildren).color = Color.white; } ((TMP_Text)componentInChildren).alignment = (TextAlignmentOptions)514; } Button componentInChildren2 = buttonObject.GetComponentInChildren