Decompiled source of Straftat LobbyRank v2.5.1

LobbyRank.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using ComputerysModdingUtilities;
using HarmonyLib;
using HeathenEngineering.SteamworksIntegration;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: StraftatMod(true)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LobbyRank")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("2.5.1.0")]
[assembly: AssemblyInformationalVersion("2.5.1+606f9c8ba68a1f1c4a488c2551b28b9d48f43dc0")]
[assembly: AssemblyProduct("Straftat-LobbyRank")]
[assembly: AssemblyTitle("LobbyRank")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.5.1.0")]
[module: UnverifiableCode]
[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 LobbyRank
{
	[BepInPlugin("LobbyRank", "Straftat-LobbyRank", "2.5.1")]
	public class Plugin : BaseUnityPlugin
	{
		private const int LeaderboardChunkSize = 4;

		private const int ChunkRetryAttempts = 3;

		private const int NormalizedRanksCount = 10;

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

		private static readonly List<LeaderboardEntry> CachedTop10Entries = new List<LeaderboardEntry>();

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

		private static int _rankOffset;

		private static bool _isTop10Ready;

		private static bool _isFetchingRanks;

		private static ManualLogSource Logger;

		private static Plugin _instance;

		private static readonly Dictionary<ulong, string> StashedNames = new Dictionary<ulong, string>();

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

		private static IEnumerator FetchWhiteListCoroutine()
		{
			if (_isFetchingRanks)
			{
				yield break;
			}
			_isFetchingRanks = true;
			ResetComputedRanks();
			try
			{
				UnityWebRequest request = UnityWebRequest.Get("https://raw.githubusercontent.com/C0mputery/StraftatLeaderboardWhitelist/refs/heads/main/Whitelist");
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					Logger.LogError((object)("Whitelist fetch result: error (" + request.error + ")"));
					yield break;
				}
				try
				{
					ulong result;
					IEnumerable<ulong> ids = (from id in request.downloadHandler.text.Split('\n', '\r')
						select id.Trim() into id
						where !string.IsNullOrWhiteSpace(id) && ulong.TryParse(id, out result)
						select id).Select(ulong.Parse);
					WhitelistedSteamIDs.Clear();
					WhitelistedSteamIDs.UnionWith(ids);
					Logger.LogInfo((object)$"Whitelist fetch result: success ({WhitelistedSteamIDs.Count} entries)");
				}
				catch (Exception ex)
				{
					Exception e = ex;
					Logger.LogError((object)("Whitelist fetch result: error (parse failed: " + e.Message + ")"));
					yield break;
				}
				LeaderboardManager lb = null;
				yield return (object)new WaitUntil((Func<bool>)delegate
				{
					if (!Object.op_Implicit((Object)(object)Settings.Instance))
					{
						return false;
					}
					lb = Settings.Instance.leaderboardManager;
					return Object.op_Implicit((Object)(object)lb?.leaderboard) && lb.leaderboard.Valid;
				});
				yield return ((MonoBehaviour)_instance).StartCoroutine(FetchWhitelistRanksCoroutine(lb));
			}
			finally
			{
				_isFetchingRanks = false;
			}
		}

		private static IEnumerator FetchWhitelistRanksCoroutine(LeaderboardManager lb)
		{
			ResetComputedRanks();
			UserData[] userData = ((IEnumerable<ulong>)WhitelistedSteamIDs).Select((Func<ulong, UserData>)UserData.Get).ToArray();
			if (userData.Length == 0)
			{
				Logger.LogError((object)"Whitelist rank fetch result: error (whitelist is empty)");
				yield break;
			}
			List<LeaderboardEntry> fetchedWhitelistEntries = new List<LeaderboardEntry>();
			bool hadWhitelistFetchError = false;
			foreach (UserData[] chunk in ChunkUserData(userData, 4))
			{
				bool chunkSucceeded = false;
				for (int attempt = 1; attempt <= 3; attempt++)
				{
					bool chunkDone = false;
					bool chunkError = false;
					LeaderboardEntry[] chunkResults = Array.Empty<LeaderboardEntry>();
					lb.leaderboard.GetEntries(chunk, (Action<LeaderboardEntry[], bool>)delegate(LeaderboardEntry[] results, bool error)
					{
						chunkError = error;
						chunkResults = results ?? Array.Empty<LeaderboardEntry>();
						chunkDone = true;
					});
					yield return (object)new WaitUntil((Func<bool>)(() => chunkDone));
					if (!chunkError)
					{
						if (chunkResults.Length != 0)
						{
							fetchedWhitelistEntries.AddRange(chunkResults);
						}
						chunkSucceeded = true;
						break;
					}
					Logger.LogError((object)((attempt < 3) ? $"Whitelist rank fetch error: chunk failed (attempt {attempt}/{3}), retrying" : $"Whitelist rank fetch error: chunk failed after {3} attempts"));
				}
				if (!chunkSucceeded)
				{
					hadWhitelistFetchError = true;
				}
			}
			Logger.LogInfo((object)(hadWhitelistFetchError ? "Whitelist rank fetch result: error (one or more chunks failed)" : $"Whitelist rank fetch result: success ({fetchedWhitelistEntries.Count} entries)"));
			if (!hadWhitelistFetchError)
			{
				LeaderboardEntry[] whitelistedTop10 = fetchedWhitelistEntries.OrderBy((LeaderboardEntry entry) => entry.Rank).Take(10).ToArray();
				for (int i = 0; i < whitelistedTop10.Length; i++)
				{
					CachedTop10Entries.Add(whitelistedTop10[i]);
					Dictionary<ulong, int> top10NormalizedRanks = Top10NormalizedRanks;
					UserData user = whitelistedTop10[i].User;
					top10NormalizedRanks[((UserData)(ref user)).SteamId] = i + 1;
				}
				int lastActualRank = whitelistedTop10[^1].Rank;
				_rankOffset = Math.Max(0, lastActualRank - 10);
				Logger.LogInfo((object)$"Cheaters in top 10: {_rankOffset} (cachedTop10={CachedTop10Entries.Count})");
				_isTop10Ready = true;
				Logger.LogInfo((object)$"top 10 ready, cleared {StashedNames.Count} cached entries");
				StashedNames.Clear();
			}
		}

		private static string GetNormalizedRank(int rawRank, ulong steamId)
		{
			if (!_isTop10Ready)
			{
				return $"~{rawRank}";
			}
			if (Top10NormalizedRanks.TryGetValue(steamId, out var value))
			{
				return $"<#efbf04>#{value}</color>";
			}
			int num = rawRank - _rankOffset;
			return (num > 10) ? $"#{num}" : $"<#ff0000>!{rawRank}</color>";
		}

		private static void ResetComputedRanks()
		{
			_isTop10Ready = false;
			_rankOffset = 0;
			CachedTop10Entries.Clear();
			Top10NormalizedRanks.Clear();
		}

		private static List<UserData[]> ChunkUserData(UserData[] userData, int chunkSize)
		{
			List<UserData[]> list = new List<UserData[]>();
			for (int i = 0; i < userData.Length; i += chunkSize)
			{
				int num = Math.Min(chunkSize, userData.Length - i);
				UserData[] array = (UserData[])(object)new UserData[num];
				Array.Copy(userData, i, array, 0, num);
				list.Add(array);
			}
			return list;
		}

		public void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			_instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			Logger.LogInfo((object)"LobbyRank by forrest loaded, come se fosse antani");
			Harmony val = new Harmony("com.LobbyRank.patch");
			val.PatchAll();
			((MonoBehaviour)_instance).StartCoroutine(FetchWhiteListCoroutine());
		}

		public static void ClearStashedNames(LobbyController lobby)
		{
			if (StashedNames.Count <= 4)
			{
				return;
			}
			try
			{
				HashSet<ulong> second = lobby.PlayerIdToListItem.Values.Select((PlayerListItems items) => items.PlayerListItem.PlayerSteamID).ToHashSet();
				List<ulong> list = StashedNames.Keys.Except(second).ToList();
				foreach (ulong item in list)
				{
					StashedNames.Remove(item);
					Logger.LogInfo((object)$"removed {item} (no longer in lobby)");
				}
				if (list.Count > 0)
				{
					Logger.LogInfo((object)string.Format("cleared {0} stale entries, {1} remaining ({2})", list.Count, StashedNames.Count, string.Join(",", StashedNames.Values)));
				}
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Error in ClearStashedNames: {arg}");
			}
		}

		public static void MaybePatchName(PlayerListItem listItem)
		{
			try
			{
				if (StashedNames.TryGetValue(listItem.PlayerSteamID, out var value))
				{
					((TMP_Text)listItem.PlayerNameText).text = value;
				}
				else
				{
					UpdateSingleName(listItem);
				}
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Error in MaybePatchName for {listItem.PlayerName}: {arg}");
			}
		}

		private static void UpdateSingleName(PlayerListItem listItem)
		{
			//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)
			ulong steamId = listItem.PlayerSteamID;
			if (!InFlightFetches.Add(steamId))
			{
				return;
			}
			string playerName = listItem.PlayerName;
			Logger.LogInfo((object)$"fetching data for {playerName}  ({steamId})");
			LeaderboardManager leaderboardManager = Settings.Instance.leaderboardManager;
			if ((Object)(object)leaderboardManager?.leaderboard == (Object)null || !leaderboardManager.leaderboard.Valid)
			{
				Logger.LogError((object)$"leaderboard not ready [{playerName} ({steamId})]");
				InFlightFetches.Remove(steamId);
				return;
			}
			leaderboardManager.leaderboard.GetEntries((UserData[])(object)new UserData[1] { UserData.Get(steamId) }, (Action<LeaderboardEntry[], bool>)delegate(LeaderboardEntry[] results, bool error)
			{
				try
				{
					if (error || results == null || results.Length == 0)
					{
						Logger.LogError((object)string.Format("UpdateSingleName: GetEntries failed for {0} ({1}) error={2} results={3}, NOT stashing", playerName, steamId, error, (results == null) ? "null" : results.Length.ToString()));
					}
					else
					{
						LeaderboardEntry val = results[0];
						string normalizedRank = GetNormalizedRank(val.Rank, steamId);
						string text = $"{playerName}\n{normalizedRank} ({val.Score})";
						StashedNames[steamId] = text;
						if ((Object)(object)listItem == (Object)null || (Object)(object)listItem.PlayerNameText == (Object)null || listItem.PlayerSteamID != steamId)
						{
							Logger.LogInfo((object)$"UpdateSingleName: skipped UI write for {playerName} ({steamId}), listItem destroyed or different owner ({listItem.PlayerSteamID})");
						}
						else
						{
							((TMP_Text)listItem.PlayerNameText).text = text;
						}
					}
				}
				catch (Exception arg)
				{
					Logger.LogError((object)$"UpdateSingleName: error in callback for {playerName} ({steamId}): {arg}");
				}
				finally
				{
					InFlightFetches.Remove(steamId);
				}
			});
		}
	}
	[HarmonyPatch(typeof(PlayerListItem))]
	[HarmonyPatch("SetPlayerValues")]
	internal class SetPlayerValuesHook
	{
		private static void Postfix(PlayerListItem __instance)
		{
			Plugin.MaybePatchName(__instance);
		}
	}
	[HarmonyPatch(typeof(LobbyController))]
	[HarmonyPatch("UpdatePlayerList")]
	internal class RemovePlayerHook
	{
		private static void Postfix(LobbyController __instance)
		{
			Plugin.ClearStashedNames(__instance);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "LobbyRank";

		public const string PLUGIN_NAME = "Straftat-LobbyRank";

		public const string PLUGIN_VERSION = "2.5.1";
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}