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)
{
}
}
}