Decompiled source of LobbyRanks v1.2.1

LobbyRanks/LobbyRanks.dll

Decompiled 3 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ComputerysModdingUtilities;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: StraftatMod(true)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LobbyRanks
{
	[BepInPlugin("landa.straftat.lobbyranks", "Lobby Ranks", "1.2.1")]
	public class Plugin : BaseUnityPlugin
	{
		internal const string DefaultWhitelistUrl = "https://raw.githubusercontent.com/C0mputery/StraftatLeaderboardWhitelist/refs/heads/main/Whitelist";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> ShowScore;

		internal static ConfigEntry<bool> HighlightSelf;

		internal static ConfigEntry<bool> CheaterAdjusted;

		internal static ConfigEntry<float> PanelX;

		internal static ConfigEntry<float> PanelY;

		internal static ConfigEntry<float> Scale;

		internal static ConfigEntry<KeyboardShortcut> ToggleKey;

		internal static ConfigEntry<string> ApiNameOverride;

		internal static ConfigEntry<string> WhitelistUrl;

		private void Awake()
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Expected O, but got Unknown
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Expected O, but got Unknown
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Expected O, but got Unknown
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Expected O, but got Unknown
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("LobbyRanks.Display", "Enabled", true, "Master toggle for the lobby rank panel.");
			ShowScore = ((BaseUnityPlugin)this).Config.Bind<bool>("LobbyRanks.Display", "ShowScore", true, "Show each player's raw leaderboard score (gamesWon*4 + roundsWon) beside their global rank.");
			HighlightSelf = ((BaseUnityPlugin)this).Config.Bind<bool>("LobbyRanks.Display", "HighlightYou", true, "Tint your own row so you can spot it at a glance.");
			CheaterAdjusted = ((BaseUnityPlugin)this).Config.Bind<bool>("LobbyRanks.Display", "CheaterAdjusted", true, "Adjust ranks for known cheaters using C0mputery's whitelist so they match the in-game leaderboard. Players sitting in the top ranks who aren't on the whitelist are flagged. Turn off to show Steam's raw global rank instead.");
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("LobbyRanks.Display", "ToggleKey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Hotkey to show/hide the panel.");
			PanelX = ((BaseUnityPlugin)this).Config.Bind<float>("LobbyRanks.Panel", "CenterX", 0.5f, new ConfigDescription("Panel horizontal center as a fraction of screen width.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			PanelY = ((BaseUnityPlugin)this).Config.Bind<float>("LobbyRanks.Panel", "TopY", 0.1f, new ConfigDescription("Panel top edge as a fraction of screen height.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.9f), Array.Empty<object>()));
			Scale = ((BaseUnityPlugin)this).Config.Bind<float>("LobbyRanks.Panel", "Scale", 1f, new ConfigDescription("Panel scale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.6f, 2f), Array.Empty<object>()));
			ApiNameOverride = ((BaseUnityPlugin)this).Config.Bind<string>("LobbyRanks.Advanced", "LeaderboardApiName", "", "Leave blank to use the same leaderboard the game itself resolved (read off its LeaderboardManager). Only set this if that fails - the resolved name is logged once at startup.");
			WhitelistUrl = ((BaseUnityPlugin)this).Config.Bind<string>("LobbyRanks.Advanced", "WhitelistUrl", "https://raw.githubusercontent.com/C0mputery/StraftatLeaderboardWhitelist/refs/heads/main/Whitelist", "URL of the newline-delimited SteamID64 whitelist used to filter cheaters out of the ranking.");
			GameObject val = new GameObject("LobbyRanks");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<LobbyRanksBehaviour>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Lobby Ranks loaded.");
		}
	}
	internal enum RankKind
	{
		BoardPending,
		Unranked,
		Provisional,
		Top10,
		Normal,
		Cheater
	}
	internal struct PlayerRow
	{
		public ulong SteamId;

		public string Name;

		public bool IsLocal;

		public int Rank;

		public int Score;

		public bool Ranked;
	}
	internal static class Ranks
	{
		private enum State
		{
			Idle,
			Finding,
			Ready,
			Failed
		}

		private static State _state = State.Idle;

		private static SteamLeaderboard_t _board;

		private static string _apiName;

		private static float _retryAt = -999f;

		private static CallResult<LeaderboardFindResult_t> _findCall;

		private static CallResult<LeaderboardScoresDownloaded_t> _downloadCall;

		private static readonly Dictionary<ulong, PlayerRow> _cache = new Dictionary<ulong, PlayerRow>();

		private static HashSet<ulong> _lastRequested = new HashSet<ulong>();

		private static readonly HashSet<ulong> _requestScratch = new HashSet<ulong>();

		private static readonly HashSet<ulong> _downloadedScratch = new HashSet<ulong>();

		private static readonly List<ulong> _pruneKeys = new List<ulong>();

		private static bool _requestInFlight;

		private static float _lastRequestAt = -999f;

		private static int _dataVersion;

		private const float MinRequestInterval = 4f;

		private static readonly Dictionary<ulong, int> _top10Rank = new Dictionary<ulong, int>();

		private static int _rankOffset;

		private static bool _normReady;

		internal const int MaxUsersPerRequest = 100;

		internal static bool BoardReady => _state == State.Ready;

		internal static SteamLeaderboard_t Board => _board;

		internal static int DataVersion => _dataVersion;

		internal static void EnsureBoard(float now)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if (_state == State.Finding || _state == State.Ready || (_state == State.Failed && now < _retryAt))
			{
				return;
			}
			string text = ResolveApiName();
			if (string.IsNullOrEmpty(text))
			{
				_state = State.Failed;
				_retryAt = now + 15f;
				return;
			}
			try
			{
				if (_findCall == null)
				{
					_findCall = CallResult<LeaderboardFindResult_t>.Create((APIDispatchDelegate<LeaderboardFindResult_t>)OnFind);
				}
				_apiName = text;
				_state = State.Finding;
				_retryAt = now + 15f;
				SteamAPICall_t val = SteamUserStats.FindLeaderboard(text);
				_findCall.Set(val, (APIDispatchDelegate<LeaderboardFindResult_t>)null);
				Plugin.Log.LogInfo((object)("[LobbyRanks] Looking up leaderboard '" + text + "'..."));
			}
			catch (Exception ex)
			{
				_state = State.Failed;
				_retryAt = now + 15f;
				Plugin.Log.LogWarning((object)("[LobbyRanks] FindLeaderboard deferred: " + ex.Message));
			}
		}

		private static void OnFind(LeaderboardFindResult_t result, bool ioFailure)
		{
			//IL_0003: 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)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (ioFailure || result.m_bLeaderboardFound == 0)
			{
				_state = State.Failed;
				_dataVersion++;
				Plugin.Log.LogWarning((object)$"[LobbyRanks] Leaderboard '{_apiName}' not found (ioFailure={ioFailure}).");
			}
			else
			{
				_board = result.m_hSteamLeaderboard;
				_state = State.Ready;
				_dataVersion++;
				Plugin.Log.LogInfo((object)("[LobbyRanks] Leaderboard '" + _apiName + "' resolved."));
			}
		}

		internal static void Refresh(IReadOnlyList<ulong> roster, float now)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: 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)
			if (_state != State.Ready || _requestInFlight || roster.Count == 0)
			{
				return;
			}
			_requestScratch.Clear();
			int num = Math.Min(roster.Count, 100);
			for (int i = 0; i < num; i++)
			{
				_requestScratch.Add(roster[i]);
			}
			if ((_requestScratch.SetEquals(_lastRequested) && now - _lastRequestAt < 30f) || now - _lastRequestAt < 4f)
			{
				return;
			}
			try
			{
				if (_downloadCall == null)
				{
					_downloadCall = CallResult<LeaderboardScoresDownloaded_t>.Create((APIDispatchDelegate<LeaderboardScoresDownloaded_t>)OnDownload);
				}
				CSteamID[] array = (CSteamID[])(object)new CSteamID[num];
				for (int j = 0; j < num; j++)
				{
					array[j] = new CSteamID(roster[j]);
				}
				SteamAPICall_t val = SteamUserStats.DownloadLeaderboardEntriesForUsers(_board, array, array.Length);
				_downloadCall.Set(val, (APIDispatchDelegate<LeaderboardScoresDownloaded_t>)null);
				_requestInFlight = true;
				_lastRequested.Clear();
				foreach (ulong item in _requestScratch)
				{
					_lastRequested.Add(item);
				}
				_lastRequestAt = now;
			}
			catch (Exception ex)
			{
				_requestInFlight = false;
				Plugin.Log.LogWarning((object)("[LobbyRanks] Download request failed: " + ex.Message));
			}
		}

		private static void OnDownload(LeaderboardScoresDownloaded_t result, bool ioFailure)
		{
			//IL_0094: 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_0028: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			_requestInFlight = false;
			if (ioFailure)
			{
				Plugin.Log.LogWarning((object)"[LobbyRanks] Leaderboard download failed (ioFailure).");
				return;
			}
			_downloadedScratch.Clear();
			LeaderboardEntry_t val = default(LeaderboardEntry_t);
			for (int i = 0; i < result.m_cEntryCount; i++)
			{
				if (SteamUserStats.GetDownloadedLeaderboardEntry(result.m_hSteamLeaderboardEntries, i, ref val, (int[])null, 0))
				{
					ulong steamID = val.m_steamIDUser.m_SteamID;
					_downloadedScratch.Add(steamID);
					_cache[steamID] = new PlayerRow
					{
						SteamId = steamID,
						Rank = val.m_nGlobalRank,
						Score = val.m_nScore,
						Ranked = true
					};
				}
			}
			foreach (ulong item in _lastRequested)
			{
				if (!_downloadedScratch.Contains(item))
				{
					_cache[item] = new PlayerRow
					{
						SteamId = item,
						Rank = 0,
						Score = 0,
						Ranked = false
					};
				}
			}
			_pruneKeys.Clear();
			foreach (ulong key in _cache.Keys)
			{
				if (!_lastRequested.Contains(key))
				{
					_pruneKeys.Add(key);
				}
			}
			foreach (ulong pruneKey in _pruneKeys)
			{
				_cache.Remove(pruneKey);
			}
			_dataVersion++;
		}

		internal static bool TryGet(ulong id, out PlayerRow row)
		{
			return _cache.TryGetValue(id, out row);
		}

		internal static void SetNormalization(Dictionary<ulong, int> top10, int offset)
		{
			_top10Rank.Clear();
			foreach (KeyValuePair<ulong, int> item in top10)
			{
				_top10Rank[item.Key] = item.Value;
			}
			_rankOffset = offset;
			_normReady = true;
			_dataVersion++;
		}

		internal static (string text, RankKind kind) Display(ulong steamId, int rawRank, bool ranked)
		{
			if (!BoardReady)
			{
				return (text: "…", kind: RankKind.BoardPending);
			}
			if (!ranked || rawRank <= 0)
			{
				return (text: "—", kind: RankKind.Unranked);
			}
			if (!Plugin.CheaterAdjusted.Value)
			{
				return (text: "#" + rawRank.ToString("N0"), kind: RankKind.Normal);
			}
			if (!_normReady)
			{
				return (text: "~" + rawRank.ToString("N0"), kind: RankKind.Provisional);
			}
			if (_top10Rank.TryGetValue(steamId, out var value))
			{
				return (text: "#" + value.ToString("N0"), kind: RankKind.Top10);
			}
			int num = rawRank - _rankOffset;
			if (num > 10)
			{
				return (text: "#" + num.ToString("N0"), kind: RankKind.Normal);
			}
			return (text: "!" + rawRank.ToString("N0"), kind: RankKind.Cheater);
		}

		private static string ResolveApiName()
		{
			string value = Plugin.ApiNameOverride.Value;
			if (!string.IsNullOrEmpty(value))
			{
				return value;
			}
			string text = ReadGameLeaderboardApiName();
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			Type type = AccessTools.TypeByName("HeathenEngineering.SteamworksIntegration.LeaderboardObject");
			if (type == null)
			{
				return null;
			}
			FieldInfo fieldInfo = AccessTools.Field(type, "apiName");
			if (fieldInfo == null)
			{
				return null;
			}
			Object[] array = Resources.FindObjectsOfTypeAll(type);
			foreach (Object obj in array)
			{
				if (fieldInfo.GetValue(obj) is string text2 && !string.IsNullOrEmpty(text2) && !text2.Equals("TestLeaderboard", StringComparison.OrdinalIgnoreCase))
				{
					return text2;
				}
			}
			return null;
		}

		private static string ReadGameLeaderboardApiName()
		{
			try
			{
				Type type = AccessTools.TypeByName("Settings");
				if (type == null)
				{
					return null;
				}
				object obj = AccessTools.Field(type, "Instance")?.GetValue(null);
				if (obj == null)
				{
					return null;
				}
				object obj2 = AccessTools.Field(type, "leaderboardManager")?.GetValue(obj);
				if (obj2 == null)
				{
					return null;
				}
				object obj3 = AccessTools.Field(obj2.GetType(), "leaderboard")?.GetValue(obj2);
				if (obj3 == null)
				{
					return null;
				}
				return AccessTools.Field(obj3.GetType(), "apiName")?.GetValue(obj3) as string;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[LobbyRanks] Could not read the game's leaderboard name: " + ex.Message));
				return null;
			}
		}
	}
	internal class LobbyRanksBehaviour : MonoBehaviour
	{
		private sealed class DisplayRow
		{
			public PlayerRow Player;

			public RankKind RankKind;

			public readonly GUIContent RankContent = new GUIContent();

			public readonly GUIContent NameContent = new GUIContent();

			public readonly GUIContent ScoreContent = new GUIContent();

			public float RankWidth;

			public float NameWidth;

			public float ScoreWidth;
		}

		private static FieldInfo _instancesField;

		private static FieldInfo _steamIdField;

		private static FieldInfo _nameField;

		private bool _reflectionReady;

		private bool _reflectionFailed;

		private ulong _localSteamId;

		private float _nextRosterPoll;

		private bool _hidden;

		private bool _normStarted;

		private readonly List<PlayerRow> _roster = new List<PlayerRow>();

		private readonly List<ulong> _steamIds = new List<ulong>();

		private readonly List<PlayerRow> _sortedRoster = new List<PlayerRow>();

		private readonly List<DisplayRow> _displayRows = new List<DisplayRow>();

		private readonly GUIContent _titleContent = new GUIContent("LOBBY RANKS");

		private readonly GUIContent _countContent = new GUIContent();

		private int _displayCount;

		private float _rankColumnWidth;

		private float _nameColumnWidth;

		private float _scoreColumnWidth;

		private float _headerWidth;

		private bool _layoutDirty = true;

		private int _seenRanksVersion = -1;

		private bool _stylesBuilt;

		private float _stylesScale = -1f;

		private GUIStyle _title;

		private GUIStyle _count;

		private GUIStyle _rankText;

		private GUIStyle _name;

		private GUIStyle _score;

		private GUIStyle _muted;

		private Color _cPanel;

		private Color _cBorder;

		private Color _cZebra;

		private Color _cSelf;

		private Color _cAccent;

		private Color _cAccentDim;

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = Plugin.ToggleKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				_hidden = !_hidden;
			}
			if (ResolveReflection() && !(Time.unscaledTime < _nextRosterPoll))
			{
				_nextRosterPoll = Time.unscaledTime + 1f;
				if (!Ranks.BoardReady)
				{
					Ranks.EnsureBoard(Time.unscaledTime);
				}
				if (Ranks.BoardReady && !_normStarted && Plugin.CheaterAdjusted.Value)
				{
					_normStarted = true;
					((MonoBehaviour)this).StartCoroutine(BuildNormalization());
				}
				int dataVersion = Ranks.DataVersion;
				PollRoster();
				if (_seenRanksVersion != dataVersion)
				{
					_seenRanksVersion = dataVersion;
					_layoutDirty = true;
				}
				if (_steamIds.Count > 0)
				{
					Ranks.Refresh(_steamIds, Time.unscaledTime);
				}
			}
		}

		private IEnumerator BuildNormalization()
		{
			string value = Plugin.WhitelistUrl.Value;
			UnityWebRequest req = UnityWebRequest.Get(value);
			string text;
			try
			{
				yield return req.SendWebRequest();
				if ((int)req.result != 1)
				{
					Plugin.Log.LogWarning((object)("[LobbyRanks] Whitelist fetch failed (" + req.error + "); showing raw ranks until it loads."));
					_normStarted = false;
					yield break;
				}
				text = req.downloadHandler.text;
			}
			finally
			{
				((IDisposable)req)?.Dispose();
			}
			HashSet<ulong> hashSet = new HashSet<ulong>();
			using (StringReader stringReader = new StringReader(text))
			{
				string text2;
				while ((text2 = stringReader.ReadLine()) != null)
				{
					if (ulong.TryParse(text2.Trim(), out var result))
					{
						hashSet.Add(result);
					}
				}
			}
			ulong[] whitelist = new ulong[hashSet.Count];
			hashSet.CopyTo(whitelist);
			if (whitelist.Length == 0)
			{
				Plugin.Log.LogWarning((object)"[LobbyRanks] Whitelist was empty; showing raw ranks.");
				yield break;
			}
			Plugin.Log.LogInfo((object)$"[LobbyRanks] Whitelist loaded ({whitelist.Length} accounts); resolving ranks...");
			Dictionary<ulong, int> ranks = new Dictionary<ulong, int>();
			CallResult<LeaderboardScoresDownloaded_t> call = CallResult<LeaderboardScoresDownloaded_t>.Create((APIDispatchDelegate<LeaderboardScoresDownloaded_t>)null);
			for (int offset = 0; offset < whitelist.Length; offset += 100)
			{
				int num = Math.Min(100, whitelist.Length - offset);
				CSteamID[] array = (CSteamID[])(object)new CSteamID[num];
				for (int i = 0; i < num; i++)
				{
					array[i] = new CSteamID(whitelist[offset + i]);
				}
				bool done = false;
				call.Set(SteamUserStats.DownloadLeaderboardEntriesForUsers(Ranks.Board, array, array.Length), (APIDispatchDelegate<LeaderboardScoresDownloaded_t>)delegate(LeaderboardScoresDownloaded_t res, bool ioFailure)
				{
					//IL_0048: 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_0008: Unknown result type (might be due to invalid IL or missing references)
					//IL_0019: 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_002e: 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)
					if (!ioFailure)
					{
						LeaderboardEntry_t val = default(LeaderboardEntry_t);
						for (int j = 0; j < res.m_cEntryCount; j++)
						{
							if (SteamUserStats.GetDownloadedLeaderboardEntry(res.m_hSteamLeaderboardEntries, j, ref val, (int[])null, 0) && val.m_nGlobalRank > 0)
							{
								ranks[val.m_steamIDUser.m_SteamID] = val.m_nGlobalRank;
							}
						}
					}
					done = true;
				});
				yield return (object)new WaitUntil((Func<bool>)(() => done));
			}
			if (ranks.Count == 0)
			{
				Plugin.Log.LogWarning((object)"[LobbyRanks] No whitelisted players were ranked; showing raw ranks.");
				yield break;
			}
			List<KeyValuePair<ulong, int>> list = new List<KeyValuePair<ulong, int>>(ranks);
			list.Sort((KeyValuePair<ulong, int> left, KeyValuePair<ulong, int> right) => left.Value.CompareTo(right.Value));
			if (list.Count > 10)
			{
				list.RemoveRange(10, list.Count - 10);
			}
			Dictionary<ulong, int> dictionary = new Dictionary<ulong, int>();
			for (int num2 = 0; num2 < list.Count; num2++)
			{
				dictionary[list[num2].Key] = num2 + 1;
			}
			int value2 = list[list.Count - 1].Value;
			int num3 = Math.Max(0, value2 - list.Count);
			Ranks.SetNormalization(dictionary, num3);
			Plugin.Log.LogInfo((object)$"[LobbyRanks] Ranks normalized: {list.Count} in true top-10, {num3} cheaters above them.");
		}

		private void PollRoster()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			_roster.Clear();
			_steamIds.Clear();
			if (_localSteamId == 0L)
			{
				try
				{
					_localSteamId = (ulong)SteamUser.GetSteamID();
				}
				catch
				{
				}
			}
			if (!(_instancesField.GetValue(null) is IDictionary dictionary))
			{
				return;
			}
			foreach (object value in dictionary.Values)
			{
				if (value == null)
				{
					continue;
				}
				ulong num = (ulong)_steamIdField.GetValue(value);
				if (num != 0L)
				{
					string name = (_nameField.GetValue(value) as string) ?? "?";
					PlayerRow item = new PlayerRow
					{
						SteamId = num,
						Name = name,
						IsLocal = (num == _localSteamId)
					};
					if (Ranks.TryGet(num, out var row))
					{
						item.Rank = row.Rank;
						item.Score = row.Score;
						item.Ranked = row.Ranked;
					}
					_roster.Add(item);
					_steamIds.Add(num);
				}
			}
			_layoutDirty = true;
		}

		private bool ResolveReflection()
		{
			if (_reflectionReady)
			{
				return true;
			}
			if (_reflectionFailed)
			{
				return false;
			}
			Type type = AccessTools.TypeByName("ClientInstance");
			if (type == null)
			{
				return false;
			}
			_instancesField = AccessTools.Field(type, "playerInstances");
			_steamIdField = AccessTools.Field(type, "PlayerSteamID");
			_nameField = AccessTools.Field(type, "PlayerName");
			if (_instancesField == null || _steamIdField == null || _nameField == null)
			{
				_reflectionFailed = true;
				Plugin.Log.LogError((object)"[LobbyRanks] Could not resolve ClientInstance fields; mod disabled.");
				return false;
			}
			_reflectionReady = true;
			return true;
		}

		private void RebuildDisplayRows()
		{
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_0296: 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_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			_sortedRoster.Clear();
			_sortedRoster.AddRange(_roster);
			_sortedRoster.Sort(CompareRows);
			_displayCount = _sortedRoster.Count;
			while (_displayRows.Count < _displayCount)
			{
				_displayRows.Add(new DisplayRow());
			}
			float value = Plugin.Scale.Value;
			_rankColumnWidth = 24f * value;
			_scoreColumnWidth = 6f * value;
			_nameColumnWidth = 0f;
			for (int i = 0; i < _displayCount; i++)
			{
				PlayerRow player = _sortedRoster[i];
				DisplayRow displayRow = _displayRows[i];
				displayRow.Player = player;
				GUIContent rankContent = displayRow.RankContent;
				DisplayRow displayRow2 = displayRow;
				(string, RankKind) tuple = Ranks.Display(player.SteamId, player.Rank, player.Ranked);
				rankContent.text = tuple.Item1;
				displayRow2.RankKind = tuple.Item2;
				displayRow.ScoreContent.text = (player.Ranked ? (Plugin.ShowScore.Value ? player.Score.ToString("N0") : "") : (Ranks.BoardReady ? "unranked" : ""));
				displayRow.NameContent.text = player.Name + (player.IsLocal ? "  (you)" : "");
				displayRow.RankWidth = _rankText.CalcSize(displayRow.RankContent).x;
				displayRow.ScoreWidth = (player.Ranked ? _score : _muted).CalcSize(displayRow.ScoreContent).x;
				displayRow.NameWidth = _name.CalcSize(displayRow.NameContent).x;
				_rankColumnWidth = Mathf.Max(_rankColumnWidth, displayRow.RankWidth);
				_scoreColumnWidth = Mathf.Max(_scoreColumnWidth, displayRow.ScoreWidth);
				_nameColumnWidth = Mathf.Max(_nameColumnWidth, displayRow.NameWidth);
			}
			_countContent.text = _displayCount + ((_displayCount == 1) ? " player" : " players");
			_headerWidth = _title.CalcSize(_titleContent).x + 16f * value + _count.CalcSize(_countContent).x;
			_seenRanksVersion = Ranks.DataVersion;
			_layoutDirty = false;
		}

		private static int CompareRows(PlayerRow left, PlayerRow right)
		{
			int num = ((!left.Ranked) ? 1 : 0).CompareTo((!right.Ranked) ? 1 : 0);
			if (num != 0)
			{
				return num;
			}
			if (left.Ranked && right.Ranked)
			{
				num = left.Rank.CompareTo(right.Rank);
				if (num != 0)
				{
					return num;
				}
			}
			return string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase);
		}

		private void OnGUI()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: 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_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_032c: 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_034f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0351: Unknown result type (might be due to invalid IL or missing references)
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Unknown result type (might be due to invalid IL or missing references)
			//IL_0359: Unknown result type (might be due to invalid IL or missing references)
			//IL_0375: Unknown result type (might be due to invalid IL or missing references)
			//IL_0390: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Enabled.Value || _hidden || _roster.Count == 0)
			{
				return;
			}
			Event current = Event.current;
			if (current != null && (int)current.type != 7)
			{
				return;
			}
			BuildStyles();
			if (_layoutDirty || _seenRanksVersion != Ranks.DataVersion)
			{
				RebuildDisplayRows();
			}
			float value = Plugin.Scale.Value;
			float num = 12f * value;
			float num2 = 30f * value;
			float num3 = 36f * value;
			float num4 = 10f * value;
			float num5 = 9f * value;
			float num6 = 11f * value;
			int displayCount = _displayCount;
			float num7 = _rankColumnWidth + num5 * 2f;
			float num8 = Mathf.Min(_nameColumnWidth, 260f * value);
			float num9 = Mathf.Max(num7 + num4 + num8 + num4 + _scoreColumnWidth, _headerWidth);
			float num10 = Mathf.Min(Mathf.Max(248f * value, num * 2f + num9), (float)Screen.width - 8f);
			float num11 = num3 + (float)displayCount * num2 + num;
			float num12 = Mathf.Clamp(Plugin.PanelX.Value * (float)Screen.width - num10 / 2f, 0f, Mathf.Max(0f, (float)Screen.width - num10));
			float num13 = Plugin.PanelY.Value * (float)Screen.height;
			Fill(new Rect(num12, num13, num10, num11), _cBorder, num6);
			Fill(new Rect(num12 + 1.5f, num13 + 1.5f, num10 - 3f, num11 - 3f), _cPanel, num6 - 1f);
			Rect val = new Rect(num12 + num, num13, num10 - num * 2f, num3);
			ShadowLabel(val, _titleContent, _title);
			GUI.Label(val, _countContent, _count);
			Fill(new Rect(num12 + num, num13 + num3 - 3f * value, num10 - num * 2f, 1.5f * value), _cAccentDim, 1f);
			float num14 = num12 + num + num7 + num4;
			float num15 = num12 + num10 - num;
			float num16 = num13 + num3;
			Rect r = default(Rect);
			for (int i = 0; i < displayCount; i++)
			{
				DisplayRow displayRow = _displayRows[i];
				PlayerRow player = displayRow.Player;
				((Rect)(ref r))..ctor(num12 + 5f * value, num16 + 1f * value, num10 - 10f * value, num2 - 2f * value);
				if ((i & 1) == 1)
				{
					Fill(r, _cZebra, 6f * value);
				}
				if (player.IsLocal && Plugin.HighlightSelf.Value)
				{
					Fill(r, _cSelf, 6f * value);
					Fill(new Rect(num12 + 5f * value, num16 + 5f * value, 3f * value, num2 - 10f * value), _cAccent, 1.5f * value);
				}
				float num17 = num2 - 10f * value;
				Rect r2 = new Rect(num12 + num, num16 + 5f * value, num7, num17);
				var (c, color) = BadgeColors(displayRow.RankKind, displayRow.RankContent.text);
				Fill(r2, c, num17 * 0.35f);
				ShadowLabel(r2, displayRow.RankContent, _rankText, color);
				ShadowLabel(new Rect(num14, num16, num15 - _scoreColumnWidth - num4 - num14, num2), displayRow.NameContent, _name);
				GUI.Label(new Rect(num15 - _scoreColumnWidth, num16, _scoreColumnWidth, num2), displayRow.ScoreContent, player.Ranked ? _score : _muted);
				num16 += num2;
			}
		}

		private static void Fill(Rect r, Color c, float radius)
		{
			//IL_0000: 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)
			GUI.DrawTexture(r, (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0, true, 0f, c, 0f, radius);
		}

		private static void ShadowLabel(Rect r, GUIContent content, GUIStyle style)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			ShadowLabel(r, content, style, style.normal.textColor);
		}

		private static void ShadowLabel(Rect r, GUIContent content, GUIStyle style, Color color)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_006a: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			Color textColor = style.normal.textColor;
			style.normal.textColor = new Color(0f, 0f, 0f, 0.5f);
			GUI.Label(new Rect(((Rect)(ref r)).x + 1f, ((Rect)(ref r)).y + 1.5f, ((Rect)(ref r)).width, ((Rect)(ref r)).height), content, style);
			style.normal.textColor = color;
			GUI.Label(r, content, style);
			style.normal.textColor = textColor;
		}

		private static int ParseNum(string text)
		{
			int num = 0;
			bool flag = false;
			foreach (char c in text)
			{
				if (c >= '0' && c <= '9')
				{
					num = num * 10 + (c - 48);
					flag = true;
				}
				else if (flag)
				{
					break;
				}
			}
			if (!flag)
			{
				return -1;
			}
			return num;
		}

		private static (Color bg, Color fg) BadgeColors(RankKind kind, string text)
		{
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: 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_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			return kind switch
			{
				RankKind.Top10 => ParseNum(text) switch
				{
					1 => (bg: new Color(1f, 0.84f, 0.3f, 0.22f), fg: new Color(1f, 0.89f, 0.48f)), 
					2 => (bg: new Color(0.82f, 0.86f, 0.92f, 0.2f), fg: new Color(0.93f, 0.96f, 1f)), 
					3 => (bg: new Color(0.8f, 0.52f, 0.3f, 0.24f), fg: new Color(1f, 0.74f, 0.48f)), 
					_ => (bg: new Color(1f, 0.84f, 0.3f, 0.14f), fg: new Color(1f, 0.86f, 0.42f)), 
				}, 
				RankKind.Cheater => (bg: new Color(1f, 0.3f, 0.3f, 0.2f), fg: new Color(1f, 0.47f, 0.44f)), 
				RankKind.Normal => (bg: new Color(1f, 1f, 1f, 0.07f), fg: new Color(0.92f, 0.94f, 0.98f)), 
				_ => (bg: new Color(1f, 1f, 1f, 0.035f), fg: new Color(0.55f, 0.58f, 0.64f)), 
			};
		}

		private void BuildStyles()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Expected O, but got Unknown
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Expected O, but got Unknown
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Expected O, but got Unknown
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Expected O, but got Unknown
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Expected O, but got Unknown
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			float value = Plugin.Scale.Value;
			if (!_stylesBuilt || !Mathf.Approximately(_stylesScale, value))
			{
				_stylesBuilt = true;
				_stylesScale = value;
				_layoutDirty = true;
				int fontSize = Mathf.RoundToInt(15f * value);
				_cPanel = new Color(0.07f, 0.08f, 0.1f, 0.93f);
				_cBorder = new Color(1f, 1f, 1f, 0.1f);
				_cZebra = new Color(1f, 1f, 1f, 0.03f);
				_cSelf = new Color(0.22f, 0.55f, 0.32f, 0.22f);
				_cAccent = new Color(1f, 0.84f, 0.3f, 1f);
				_cAccentDim = new Color(1f, 0.84f, 0.3f, 0.45f);
				_title = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)3,
					fontStyle = (FontStyle)1,
					fontSize = fontSize
				};
				_title.normal.textColor = new Color(0.97f, 0.95f, 0.86f);
				_count = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)5,
					fontSize = Mathf.RoundToInt(12f * value)
				};
				_count.normal.textColor = new Color(0.62f, 0.66f, 0.72f);
				_rankText = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)4,
					fontStyle = (FontStyle)1,
					fontSize = fontSize
				};
				_rankText.normal.textColor = Color.white;
				_name = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)3,
					fontSize = fontSize,
					clipping = (TextClipping)1
				};
				_name.normal.textColor = new Color(0.94f, 0.95f, 0.97f);
				_score = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)5,
					fontSize = Mathf.RoundToInt(13f * value)
				};
				_score.normal.textColor = new Color(0.8f, 0.84f, 0.9f);
				_muted = new GUIStyle(_score)
				{
					fontStyle = (FontStyle)2
				};
				_muted.normal.textColor = new Color(0.5f, 0.53f, 0.59f);
			}
		}
	}
}