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 WhitelistedSteamIDs = new HashSet(); private static readonly List CachedTop10Entries = new List(); private static readonly Dictionary Top10NormalizedRanks = new Dictionary(); private static int _rankOffset; private static bool _isTop10Ready; private static bool _isFetchingRanks; private static ManualLogSource Logger; private static Plugin _instance; private static readonly Dictionary StashedNames = new Dictionary(); private static readonly HashSet InFlightFetches = new HashSet(); 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 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)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)WhitelistedSteamIDs).Select((Func)UserData.Get).ToArray(); if (userData.Length == 0) { Logger.LogError((object)"Whitelist rank fetch result: error (whitelist is empty)"); yield break; } List fetchedWhitelistEntries = new List(); 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(); lb.leaderboard.GetEntries(chunk, (Action)delegate(LeaderboardEntry[] results, bool error) { chunkError = error; chunkResults = results ?? Array.Empty(); chunkDone = true; }); yield return (object)new WaitUntil((Func)(() => 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 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}"; } int num = rawRank - _rankOffset; return (num > 10) ? $"#{num}" : $"<#ff0000>!{rawRank}"; } private static void ResetComputedRanks() { _isTop10Ready = false; _rankOffset = 0; CachedTop10Entries.Clear(); Top10NormalizedRanks.Clear(); } private static List ChunkUserData(UserData[] userData, int chunkSize) { List list = new List(); 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 second = lobby.PlayerIdToListItem.Values.Select((PlayerListItems items) => items.PlayerListItem.PlayerSteamID).ToHashSet(); List 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)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) { } } }