using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Peak.Network; using Photon.Pun; using Photon.Realtime; using PhotonCustomPropsUtils; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("com.github.LengSword.BetterModVerifier")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+4ab16c2d2e7263c8b1af4d6e1b462d67808a96c1")] [assembly: AssemblyProduct("com.github.LengSword.BetterModVerifier")] [assembly: AssemblyTitle("BetterModVerifier")] [assembly: AssemblyMetadata("RepositoryUrl", "https://codeberg.org/yls-peak-mods/BetterModVerifier")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] 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 BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Embedded] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Embedded] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace Microsoft.CodeAnalysis { [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace BetterModVerifier { [Serializable] internal class ModMetadata { [JsonProperty("name")] public string Name { get; set; } = string.Empty; [JsonProperty("guid")] public string GUID { get; set; } = string.Empty; [JsonProperty("version")] public string Version { get; set; } = string.Empty; [JsonProperty("sha256")] public string SHA256 { get; set; } = string.Empty; } [Serializable] internal class IgnoredModEntry { [JsonProperty("name")] public string Name { get; set; } = string.Empty; [JsonProperty("guid")] public string GUID { get; set; } = string.Empty; } internal class VerificationResult { public IReadOnlyList LoadedMods { get; } public IReadOnlyList ModDetails { get; } public int UnauthorizedCount { get; } public bool Succeeded => UnauthorizedCount == 0; public int MatchedRequiredModCount { get; } public int RequiredModCount { get; } public string? ErrorMessage { get; } public VerificationReport Report { get; } public VerificationResult(IReadOnlyList loadedMods, IReadOnlyList modDetails, int unauthorizedCount, int matchedRequiredModCount, int requiredModCount, VerificationReport report, string? errorMessage = null) { LoadedMods = loadedMods; ModDetails = modDetails; UnauthorizedCount = unauthorizedCount; MatchedRequiredModCount = matchedRequiredModCount; RequiredModCount = requiredModCount; Report = report; ErrorMessage = errorMessage; } } internal enum ModVerificationStatus { Allowed, AllowedExtra, Missing, Unauthorized } internal class ModVerificationDetail { public ModMetadata Mod { get; } public ModVerificationStatus Status { get; } public ModVerificationDetail(ModMetadata mod, ModVerificationStatus status) { Mod = mod; Status = status; } } internal class VerificationReport { public DateTime Timestamp { get; } public VerificationReport(DateTime timestamp) { Timestamp = timestamp; } } internal static class DisplayFormat { private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss"; internal static string FormatDateTime(DateTime value) { return value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); } internal static string FormatPlayer(string playerName, int actorNumber, ulong steamId) { return $"{playerName} (#{actorNumber}) [{steamId}]"; } internal static string FormatPlayer(Player player) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) return FormatPlayer(player.NickName, player.ActorNumber, ModSyncManager.GetSteamId(player).m_SteamID); } } internal class ModScanner { public static ModMetadata GetMetadata(string filepath, string guid, string name, string version) { return new ModMetadata { GUID = guid, Name = name, Version = version, SHA256 = ComputeHash(filepath) }; } public static bool TryGetMetadata(string filepath, string guid, string name, string version, out ModMetadata metadata) { metadata = new ModMetadata(); try { metadata = GetMetadata(filepath, guid, name, version); return true; } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { return false; } } private static string ComputeHash(string filepath) { using FileStream inputStream = File.OpenRead(filepath); using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(inputStream); return BitConverter.ToString(array).Replace("-", "").ToUpperInvariant(); } } [Serializable] internal sealed class RoomConfigData { public int ProtocolVersion = 4; public bool EnableVerifier; public bool ShowReport; public VerificationMode VerificationMode; } [Serializable] internal sealed class PlayerConfigData { public int ProtocolVersion = 4; public List Mods = new List(); } [Serializable] internal sealed class PlayerVerificationData { public int ActorNumber; public ulong SteamId; public bool Succeeded; public int MatchedRequiredModCount; public int RequiredModCount; public int UnauthorizedCount; public string ErrorMessage = string.Empty; public DateTime Timestamp; } [Serializable] internal sealed class RoomVerificationData { public int ProtocolVersion = 4; public Dictionary Players = new Dictionary(); } internal sealed class ModSyncManager : MonoBehaviourPunCallbacks { internal const int ProtocolVersion = 4; private const string RoomConfigKey = "BMV_ROOM_CFG"; private const string PlayerConfigKey = "BMV_PLAYER_CFG"; private const string RoomResultsKey = "BMV_ROOM_RESULTS"; private const float PlayerDataTimeoutSeconds = 10f; private const int MaxEncodedPropertyLength = 30000; private PhotonScopedManager photonManager; private IReadOnlyList localMods = Array.Empty(); private string verifiedModsPath = string.Empty; private ManualLogSource logger; private PlayerConnectionLog playerConnectionLog; private readonly Dictionary results = new Dictionary(); private readonly Dictionary> playerModDetails = new Dictionary>(); private readonly Dictionary processedPlayerConfigs = new Dictionary(); private readonly HashSet pendingPlayers = new HashSet(); private readonly HashSet pendingKicks = new HashSet(); private readonly HashSet kickedPlayers = new HashSet(); private bool initialized; private bool deferResultsPublish; internal event Action? RoomConfigChanged; internal event Action>? VerificationChanged; internal event Action? JoinedRoom; internal void Initialize(PhotonScopedManager photonManager, IReadOnlyList localMods, string whitelistPath, ManualLogSource logger) { this.photonManager = photonManager; this.localMods = localMods; verifiedModsPath = whitelistPath; this.logger = logger; photonManager.RegisterOnJoinedRoom((Action)OnJoinedRoomFromManager); photonManager.RegisterRoomProperty("BMV_ROOM_CFG", (RoomEventType)2, (Action)OnRoomConfigReceived); photonManager.RegisterRoomProperty("BMV_ROOM_RESULTS", (RoomEventType)1, (Action)OnRoomResultsReceived); photonManager.RegisterPlayerProperty("BMV_PLAYER_CFG", (PlayerEventType)2, (Action)OnPlayerConfigReceived); initialized = true; } private void OnDestroy() { ((MonoBehaviour)this).StopAllCoroutines(); } internal void ReverifyPlayers() { if (initialized && !PhotonNetwork.OfflineMode && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient) { playerModDetails.Clear(); processedPlayerConfigs.Clear(); VerifyKnownPlayers(); } } private void OnJoinedRoomFromManager(Player localPlayer) { if (!PhotonNetwork.OfflineMode) { ResetSessionState(); this.JoinedRoom?.Invoke(); PublishLocalPlayerData(); if (PhotonNetwork.IsMasterClient) { PublishHostConfig(); VerifyKnownPlayers(); } } } public override void OnPlayerEnteredRoom(Player newPlayer) { if (initialized && !PhotonNetwork.OfflineMode && PhotonNetwork.IsMasterClient) { StartWaitingForPlayerData(newPlayer); } } public override void OnPlayerLeftRoom(Player otherPlayer) { if (initialized) { pendingPlayers.Remove(otherPlayer.ActorNumber); pendingKicks.Remove(otherPlayer.ActorNumber); kickedPlayers.Remove(otherPlayer.ActorNumber); processedPlayerConfigs.Remove(otherPlayer.ActorNumber); playerModDetails.Remove(otherPlayer.ActorNumber); if (PhotonNetwork.IsMasterClient && results.Remove(otherPlayer.ActorNumber)) { PublishResults(); } } } public override void OnMasterClientSwitched(Player newMasterClient) { if (initialized && !PhotonNetwork.OfflineMode && PhotonNetwork.IsMasterClient) { results.Clear(); playerModDetails.Clear(); processedPlayerConfigs.Clear(); ClearPendingState(); PublishHostConfig(); VerifyKnownPlayers(); } } private void PublishHostConfig() { RoomConfigData roomConfigData = new RoomConfigData { EnableVerifier = PluginConfig.EnableVerifier.Value, ShowReport = PluginConfig.ShowReport.Value, VerificationMode = PluginConfig.VerificationMode.Value }; photonManager.SetRoomProperty("BMV_ROOM_CFG", (object)Encode(roomConfigData)); ApplyRoomConfig(roomConfigData); } private void PublishLocalPlayerData() { try { PlayerConfigData value = new PlayerConfigData { Mods = localMods.ToList() }; photonManager.SetPlayerProperty("BMV_PLAYER_CFG", (object)Encode(value)); } catch (Exception ex) { logger.LogError((object)("Failed to publish local mod data: " + ex)); } } private void OnRoomConfigReceived(string encodedConfig) { if (PhotonNetwork.OfflineMode) { return; } try { ApplyRoomConfig(Decode(encodedConfig)); } catch (Exception ex) { logger.LogWarning((object)("Unable to read host configuration: " + ex.Message)); } } private void ApplyRoomConfig(RoomConfigData config) { if (config.ProtocolVersion == 4) { this.RoomConfigChanged?.Invoke(config); } } private void OnRoomResultsReceived(string encodedResults) { if (PhotonNetwork.OfflineMode) { return; } try { RoomVerificationData roomVerificationData = Decode(encodedResults); if (roomVerificationData.ProtocolVersion == 4) { this.VerificationChanged?.Invoke(roomVerificationData.Players.Values); } } catch (Exception ex) { logger.LogWarning((object)("Unable to read synchronized verification results: " + ex.Message)); } } private void OnPlayerConfigReceived(Player player, string encodedData) { if (PhotonNetwork.OfflineMode || !PhotonNetwork.IsMasterClient || player == null) { return; } pendingPlayers.Remove(player.ActorNumber); if (!PluginConfig.EnableVerifier.Value) { return; } string text = encodedData ?? string.Empty; if (results.ContainsKey(player.ActorNumber) && processedPlayerConfigs.TryGetValue(player.ActorNumber, out string value) && value == text) { return; } processedPlayerConfigs[player.ActorNumber] = text; try { PlayerConfigData playerConfigData = Decode(text); if (playerConfigData.ProtocolVersion != 4) { throw new InvalidDataException($"Unsupported protocol version {playerConfigData.ProtocolVersion}."); } VerificationResult result = ModVerifier.VerifyPlayer(verifiedModsPath, playerConfigData.Mods ?? new List(), PluginConfig.VerificationMode.Value, logger); StoreResult(player, result); } catch (Exception ex) { StoreFailure(player, "Unable to read synchronized mod data: " + ex.Message); } } private void VerifyKnownPlayers() { if (PhotonNetwork.OfflineMode) { return; } if (!PluginConfig.EnableVerifier.Value) { results.Clear(); playerModDetails.Clear(); PublishResults(); return; } Dictionary allPlayerProperties = photonManager.GetAllPlayerProperties("BMV_PLAYER_CFG"); deferResultsPublish = true; try { Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (allPlayerProperties.TryGetValue(val, out var value)) { OnPlayerConfigReceived(val, value); } else { StartWaitingForPlayerData(val); } } } finally { deferResultsPublish = false; } PublishResults(); } private void ResetSessionState() { ((MonoBehaviour)this).StopAllCoroutines(); results.Clear(); playerModDetails.Clear(); processedPlayerConfigs.Clear(); ClearPendingState(); deferResultsPublish = false; } private void ClearPendingState() { pendingPlayers.Clear(); pendingKicks.Clear(); kickedPlayers.Clear(); } private void StartWaitingForPlayerData(Player player) { if (pendingPlayers.Add(player.ActorNumber)) { ((MonoBehaviour)this).StartCoroutine(WaitForPlayerData(player)); } } private IEnumerator WaitForPlayerData(Player player) { yield return (object)new WaitForSeconds(10f); if (PhotonNetwork.IsMasterClient && pendingPlayers.Remove(player.ActorNumber)) { StoreFailure(player, "Timed out waiting for synchronized mod data."); } } private void StoreResult(Player player, VerificationResult result) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) playerModDetails[player.ActorNumber] = result.ModDetails.ToArray(); PlayerVerificationData result2 = new PlayerVerificationData { ActorNumber = player.ActorNumber, SteamId = GetSteamId(player).m_SteamID, Succeeded = result.Succeeded, MatchedRequiredModCount = result.MatchedRequiredModCount, RequiredModCount = result.RequiredModCount, UnauthorizedCount = result.UnauthorizedCount, ErrorMessage = (result.ErrorMessage ?? string.Empty), Timestamp = result.Report.Timestamp }; StoreAndPublish(player, result2); } private void StoreFailure(Player player, string error) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) playerModDetails.Remove(player.ActorNumber); StoreAndPublish(player, new PlayerVerificationData { ActorNumber = player.ActorNumber, SteamId = GetSteamId(player).m_SteamID, Succeeded = false, MatchedRequiredModCount = 0, RequiredModCount = 0, UnauthorizedCount = 0, ErrorMessage = error, Timestamp = DateTime.UtcNow }); } internal IReadOnlyDictionary> GetPlayerModDetails() { return new Dictionary>(playerModDetails); } private void StoreAndPublish(Player player, PlayerVerificationData result) { results[player.ActorNumber] = result; if (!deferResultsPublish) { PublishResults(); } if (!result.Succeeded && player != PhotonNetwork.LocalPlayer && PluginConfig.KickUnverifiedPlayers.Value && !kickedPlayers.Contains(player.ActorNumber) && pendingKicks.Add(player.ActorNumber)) { ((MonoBehaviour)this).StartCoroutine(KickWhenReady(player.ActorNumber, DisplayFormat.FormatPlayer(player))); } } private IEnumerator KickWhenReady(int actorNumber, string playerDisplayName) { PlayerVerificationData value; while (PhotonNetwork.IsMasterClient && PluginConfig.KickUnverifiedPlayers.Value && results.TryGetValue(actorNumber, out value) && !value.Succeeded) { if (NetCode.Session != null && NetCode.Session.IsHost && (Object)(object)PlayerHandler.GetPlayer(actorNumber) != (Object)null) { kickedPlayers.Add(actorNumber); logger.LogWarning((object)("Kicking unverified player " + playerDisplayName + ".")); NotifyPlayer("[BetterModVerifier] Kicked unverified player: [[userColor]]" + playerDisplayName + ""); PlayerHandler.Kick(actorNumber); break; } yield return null; } pendingKicks.Remove(actorNumber); } private bool NotifyPlayer(string message, bool sound = true) { //IL_0052: 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_008c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)playerConnectionLog == (Object)null) { playerConnectionLog = Object.FindAnyObjectByType(); } if ((Object)(object)playerConnectionLog == (Object)null) { logger.LogWarning((object)"PlayerConnectionLog not found. Cannot show notification."); return false; } StringBuilder stringBuilder = new StringBuilder(message); stringBuilder.Replace("[[userColor]]", playerConnectionLog.GetColorTag(playerConnectionLog.userColor)); message = stringBuilder.ToString(); playerConnectionLog.AddMessage(message); if (sound) { playerConnectionLog.sfxLeave.Play(default(Vector3)); } return true; } private void PublishResults() { try { photonManager.SetRoomProperty("BMV_ROOM_RESULTS", (object)Encode(new RoomVerificationData { Players = new Dictionary(results) })); } catch (Exception ex) { logger.LogError((object)("Failed to publish verification results: " + ex.Message)); } } private static string Encode(T value) { byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject((object)value, (Formatting)0)); using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) { gZipStream.Write(bytes, 0, bytes.Length); } string text = Convert.ToBase64String(memoryStream.ToArray()); if (text.Length > 30000) { throw new InvalidDataException($"Encoded Photon property is too large ({text.Length} characters)."); } return text; } private static T Decode(string encoded) { if (string.IsNullOrWhiteSpace(encoded)) { throw new InvalidDataException("The synchronized value is empty."); } byte[] buffer = Convert.FromBase64String(encoded); using MemoryStream stream = new MemoryStream(buffer); using GZipStream stream2 = new GZipStream(stream, CompressionMode.Decompress); using StreamReader streamReader = new StreamReader(stream2, Encoding.UTF8); T val = JsonConvert.DeserializeObject(streamReader.ReadToEnd()); T val2 = val; if (val2 == null) { throw new InvalidDataException("The synchronized value contains no data."); } return val2; } internal static CSteamID GetSteamId(Player player) { //IL_0026: 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) if (player != null && !string.IsNullOrEmpty(player.UserId) && ulong.TryParse(player.UserId, out var result)) { return new CSteamID(result); } return CSteamID.Nil; } } internal enum VerificationMode { ExactMatch, AllowExtra } internal static class ModVerifier { private const string IgnoredModsFileName = "ignored_mods.json"; internal static VerificationResult Verify(string verifiedModsPath, IReadOnlyList loadedMods, VerificationMode mode, ManualLogSource logger) { IgnoredModEntry[] ignoredMods = LoadIgnoredMods(verifiedModsPath); IReadOnlyList loadedMods2 = FilterIgnoredMods(loadedMods, ignoredMods); EnsureWhitelistFile(verifiedModsPath, loadedMods2); return VerifyLoadedMods(verifiedModsPath, loadedMods2, ignoredMods, mode, logger); } internal static VerificationResult VerifyPlayer(string verifiedModsPath, IReadOnlyList loadedMods, VerificationMode mode, ManualLogSource logger) { IgnoredModEntry[] ignoredMods = LoadIgnoredMods(verifiedModsPath); IReadOnlyList loadedMods2 = FilterIgnoredMods(loadedMods, ignoredMods); return VerifyLoadedMods(verifiedModsPath, loadedMods2, ignoredMods, mode, logger); } internal static int RebuildWhitelist(string verifiedModsPath, IReadOnlyList loadedMods) { IgnoredModEntry[] ignoredMods = LoadIgnoredMods(verifiedModsPath); IReadOnlyList readOnlyList = FilterIgnoredMods(loadedMods, ignoredMods); EnsureDirectory(verifiedModsPath); File.WriteAllText(verifiedModsPath, JsonConvert.SerializeObject((object)readOnlyList, (Formatting)1)); return readOnlyList.Count; } private static VerificationResult VerifyLoadedMods(string verifiedModsPath, IReadOnlyList loadedMods, IReadOnlyList ignoredMods, VerificationMode mode, ManualLogSource logger) { //IL_003f: Expected O, but got Unknown DateTime utcNow = DateTime.UtcNow; List list = new List(); int num = 0; logger.LogDebug((object)("Fetching whitelist from " + verifiedModsPath)); ModMetadata[] array; try { array = FilterIgnoredMods(LoadWhitelistFile(verifiedModsPath) ?? Array.Empty(), ignoredMods).ToArray(); } catch (JsonException ex) { JsonException innerException = ex; throw new Exception("Serializing the whitelist: malformed whitelist", (Exception?)(object)innerException); } bool[] array2 = new bool[array.Length]; int num2 = 0; foreach (ModMetadata loadedMod in loadedMods) { int num3 = FindUnmatchedIndex(array, array2, loadedMod); if (num3 >= 0) { array2[num3] = true; num2++; list.Add(new ModVerificationDetail(loadedMod, ModVerificationStatus.Allowed)); continue; } if (mode == VerificationMode.AllowExtra) { list.Add(new ModVerificationDetail(loadedMod, ModVerificationStatus.AllowedExtra)); continue; } list.Add(new ModVerificationDetail(loadedMod, ModVerificationStatus.Unauthorized)); num++; logger.LogWarning((object)("Unauthorized mod: " + loadedMod.Name + " (" + loadedMod.Version + ")")); } for (int i = 0; i < array.Length; i++) { if (!array2[i]) { ModMetadata modMetadata = array[i]; num++; list.Add(new ModVerificationDetail(modMetadata, ModVerificationStatus.Missing)); logger.LogWarning((object)("Missing required mod: " + modMetadata.Name + " (" + modMetadata.Version + ")")); } } VerificationReport report = new VerificationReport(utcNow); return new VerificationResult(loadedMods, list, num, num2, array.Length, report); } private static bool IsMatch(ModMetadata whitelistMod, ModMetadata loadedMod) { if (string.Equals(whitelistMod.GUID, loadedMod.GUID, StringComparison.OrdinalIgnoreCase) && string.Equals(whitelistMod.Version, loadedMod.Version, StringComparison.OrdinalIgnoreCase)) { return string.Equals(whitelistMod.SHA256, loadedMod.SHA256, StringComparison.OrdinalIgnoreCase); } return false; } private static int FindUnmatchedIndex(IReadOnlyList whitelist, IReadOnlyList matchedWhitelist, ModMetadata loadedMod) { for (int i = 0; i < whitelist.Count; i++) { if (!matchedWhitelist[i] && IsMatch(whitelist[i], loadedMod)) { return i; } } return -1; } private static void EnsureWhitelistFile(string verifiedModsPath, IReadOnlyList loadedMods) { if (!File.Exists(verifiedModsPath)) { EnsureDirectory(verifiedModsPath); File.WriteAllText(verifiedModsPath, JsonConvert.SerializeObject((object)loadedMods, (Formatting)1)); } } private static ModMetadata[]? LoadWhitelistFile(string filePath) { return JsonConvert.DeserializeObject(File.ReadAllText(filePath)); } private static IgnoredModEntry[] LoadIgnoredMods(string verifiedModsPath) { //IL_0045: Expected O, but got Unknown string ignoredModsPath = GetIgnoredModsPath(verifiedModsPath); if (!File.Exists(ignoredModsPath)) { EnsureDirectory(ignoredModsPath); File.WriteAllText(ignoredModsPath, JsonConvert.SerializeObject((object)Array.Empty(), (Formatting)1)); return Array.Empty(); } try { return JsonConvert.DeserializeObject(File.ReadAllText(ignoredModsPath)) ?? Array.Empty(); } catch (JsonException ex) { JsonException innerException = ex; throw new Exception("Serializing the ignored mods: malformed ignored mod list", (Exception?)(object)innerException); } } private static IReadOnlyList FilterIgnoredMods(IReadOnlyList mods, IReadOnlyList ignoredMods) { if (ignoredMods.Count == 0) { return mods; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (IgnoredModEntry ignoredMod in ignoredMods) { if (!string.IsNullOrWhiteSpace(ignoredMod.GUID)) { hashSet.Add(ignoredMod.GUID); } } if (hashSet.Count == 0) { return mods; } List list = new List(); foreach (ModMetadata mod in mods) { if (!hashSet.Contains(mod.GUID)) { list.Add(mod); } } return list; } private static string GetIgnoredModsPath(string verifiedModsPath) { string directoryName = Path.GetDirectoryName(verifiedModsPath); return Path.Combine(directoryName ?? string.Empty, "ignored_mods.json"); } private static void EnsureDirectory(string filePath) { string directoryName = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.github.LengSword.BetterModVerifier", "BetterModVerifier", "0.1.1")] public class Plugin : BaseUnityPlugin { private static readonly string DataDirectory = Path.Combine(Application.persistentDataPath, "BetterModVerifier"); private static readonly string VerifiedModsPath = Path.Combine(DataDirectory, "verified_mods.json"); private static readonly string LogPath = Path.Combine(DataDirectory, "verifier_result.log"); private GameObject managerObject; private VerifierReportManager reportManager; private ModSyncManager modSyncManager; private IReadOnlyList localMods = Array.Empty(); private bool runStarted; private bool hostShowReport = true; private bool lastOfflineMode; private DateTime lastLoggedNetworkResultTimestamp; private VerificationResult? localVerificationResult; public const string Id = "com.github.LengSword.BetterModVerifier"; internal static ManualLogSource Logger { get; private set; } = null; public static string Name => "BetterModVerifier"; public static string Version => "0.1.1"; private void Awake() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)("Plugin " + Name + " is loaded!")); PluginConfig.Initialize(((BaseUnityPlugin)this).Config, Logger); CreateManagers(); PluginConfig.ShowReport.SettingChanged += OnShowReportChanged; GameRunState.Changed += OnRunStateChanged; new Harmony("com.github.LengSword.BetterModVerifier").PatchAll(); } private void Start() { localMods = GetLoadedMods(); Logger.LogInfo((object)$"Found {localMods.Count} loaded plugins."); SetupNetworkSync(); } private void Update() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) bool offlineMode = PhotonNetwork.OfflineMode; if (offlineMode != lastOfflineMode) { lastOfflineMode = offlineMode; RefreshReportVisibility(); } if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && Input.GetKeyDown(PluginConfig.RebuildWhitelistKey.Value)) { RebuildWhitelist(); } } private void OnDestroy() { ConfigEntry showReport = PluginConfig.ShowReport; if (showReport != null) { showReport.SettingChanged -= OnShowReportChanged; } GameRunState.Changed -= OnRunStateChanged; if ((Object)(object)modSyncManager != (Object)null) { modSyncManager.RoomConfigChanged -= OnRoomConfigChanged; modSyncManager.VerificationChanged -= OnNetworkVerificationChanged; modSyncManager.JoinedRoom -= OnJoinedRoom; } if ((Object)(object)managerObject != (Object)null) { Object.Destroy((Object)(object)managerObject); } } private void CreateManagers() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) managerObject = new GameObject("BetterModVerifierManagers"); Object.DontDestroyOnLoad((Object)(object)managerObject); reportManager = managerObject.AddComponent(); reportManager.Configure(PluginConfig.ReportPos.Value, new Vector2(PluginConfig.ReportPaddingLeft.Value, PluginConfig.ReportPaddingBottom.Value)); lastOfflineMode = PhotonNetwork.OfflineMode; RefreshReportVisibility(); modSyncManager = managerObject.AddComponent(); } private void OnShowReportChanged(object sender, EventArgs eventArgs) { RefreshReportVisibility(); } private void SetupNetworkSync() { PhotonScopedManager manager = PhotonCustomPropsUtilsPlugin.GetManager("com.github.LengSword.BetterModVerifier"); modSyncManager.RoomConfigChanged += OnRoomConfigChanged; modSyncManager.VerificationChanged += OnNetworkVerificationChanged; modSyncManager.JoinedRoom += OnJoinedRoom; modSyncManager.Initialize(manager, localMods, VerifiedModsPath, Logger); } private void OnJoinedRoom() { RunLocalVerification(); } private void RunLocalVerification() { if (PhotonNetwork.OfflineMode) { Logger.LogInfo((object)"Verification is disabled in Photon offline mode."); return; } if (!PluginConfig.EnableVerifier.Value) { Logger.LogInfo((object)"Verification is disabled."); return; } Logger.LogInfo((object)"Starting verification..."); try { VerificationResult result = ModVerifier.Verify(VerifiedModsPath, localMods, PluginConfig.VerificationMode.Value, Logger); WriteAndDisplayResult(result); } catch (Exception ex) { Logger.LogError((object)("Verification failed: " + ex)); reportManager.SetReportError(ex.Message); } } private void RebuildWhitelist() { try { int num = ModVerifier.RebuildWhitelist(VerifiedModsPath, localMods); Logger.LogInfo((object)$"Rebuilt whitelist with {num} verified plugins: {VerifiedModsPath}"); RunLocalVerification(); modSyncManager.ReverifyPlayers(); } catch (Exception ex) { Logger.LogError((object)("Failed to rebuild whitelist: " + ex)); reportManager.SetReportError("Failed to rebuild whitelist: " + ex.Message); } } private void OnRoomConfigChanged(RoomConfigData config) { hostShowReport = config.ShowReport; RefreshReportVisibility(); Logger.LogDebug((object)($"Received host config: verifier={config.EnableVerifier}, " + $"mode={config.VerificationMode}, showReport={config.ShowReport}.")); } private void OnRunStateChanged(bool started) { runStarted = started; RefreshReportVisibility(); } private void RefreshReportVisibility() { bool visible = !PhotonNetwork.OfflineMode && ((PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) ? (hostShowReport && !runStarted) : PluginConfig.ShowReport.Value); reportManager.SetVisible(visible); } private void OnNetworkVerificationChanged(IReadOnlyCollection results) { if (PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom) { return; } PlayerVerificationData playerVerificationData = null; int num = 0; int num2 = 0; List list = new List(); foreach (PlayerVerificationData result in results) { if (result.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { playerVerificationData = result; } else if (PhotonNetwork.IsMasterClient) { num2++; if (result.Succeeded) { num++; } else { list.Add(GetPlayerDisplayName(result)); } } } if (PhotonNetwork.IsMasterClient) { list.Sort(StringComparer.OrdinalIgnoreCase); reportManager.SetClientSummary(num, num2, list); } else { reportManager.ClearClientSummary(); } if (playerVerificationData == null) { return; } if (!string.IsNullOrEmpty(playerVerificationData.ErrorMessage)) { reportManager.SetReportError(playerVerificationData.ErrorMessage); return; } if (playerVerificationData.Timestamp != lastLoggedNetworkResultTimestamp) { lastLoggedNetworkResultTimestamp = playerVerificationData.Timestamp; Logger.LogInfo((object)("Network verification finished with " + (playerVerificationData.Succeeded ? "success" : "fail") + " at " + DisplayFormat.FormatDateTime(playerVerificationData.Timestamp))); } if (localVerificationResult != null) { WriteReportFile(localVerificationResult, results, modSyncManager.GetPlayerModDetails()); } reportManager.SetSummary(playerVerificationData.Timestamp, playerVerificationData.Succeeded, playerVerificationData.MatchedRequiredModCount, playerVerificationData.RequiredModCount); } private static string GetPlayerDisplayName(PlayerVerificationData result) { Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (val.ActorNumber == result.ActorNumber) { return DisplayFormat.FormatPlayer(val); } } return DisplayFormat.FormatPlayer(string.Empty, result.ActorNumber, result.SteamId); } private static string GetLocalPlayerDisplayName() { if (PhotonNetwork.LocalPlayer == null) { return DisplayFormat.FormatPlayer(string.Empty, 0, 0uL); } return DisplayFormat.FormatPlayer(PhotonNetwork.LocalPlayer); } private void WriteAndDisplayResult(VerificationResult result) { localVerificationResult = result; Logger.LogInfo((object)("Verification finished with " + (result.Succeeded ? "success" : "fail") + " at " + DisplayFormat.FormatDateTime(result.Report.Timestamp))); WriteReportFile(result, null); reportManager.SetResult(result); } private void WriteReportFile(VerificationResult localResult, IReadOnlyCollection? networkResults, IReadOnlyDictionary>? playerModDetails = null) { Logger.LogInfo((object)("Writing report into " + LogPath)); File.WriteAllText(LogPath, BuildReportText(localResult, networkResults, playerModDetails)); } private static string BuildReportText(VerificationResult localResult, IReadOnlyCollection? networkResults, IReadOnlyDictionary>? playerModDetails) { using StringWriter stringWriter = new StringWriter(); stringWriter.WriteLine("=== Verification Report (" + Name + " v" + Version + ") ==="); stringWriter.WriteLine("Timestamp: " + DisplayFormat.FormatDateTime(localResult.Report.Timestamp)); stringWriter.WriteLine("Whitelist source: " + VerifiedModsPath); stringWriter.WriteLine($"Verification mode: {PluginConfig.VerificationMode.Value}"); if (networkResults == null || networkResults.Count == 0) { stringWriter.WriteLine(); WritePlayerReportSection(stringWriter, "Host", GetLocalPlayerDisplayName(), localResult.Succeeded, localResult.Report.Timestamp, localResult.MatchedRequiredModCount, localResult.RequiredModCount, localResult.UnauthorizedCount, localResult.ErrorMessage, localResult.ModDetails); return stringWriter.ToString(); } List list = new List(networkResults); list.Sort((PlayerVerificationData left, PlayerVerificationData right) => left.ActorNumber.CompareTo(right.ActorNumber)); int num = 0; foreach (PlayerVerificationData item in list) { if (item.Succeeded) { num++; } } stringWriter.WriteLine(); stringWriter.WriteLine("=== Network Verification Results ==="); stringWriter.WriteLine($"Players: {num}/{list.Count} Passed"); foreach (PlayerVerificationData item2 in list) { string role = (IsHostPlayer(item2.ActorNumber) ? "Host" : "Client"); IReadOnlyList value = null; playerModDetails?.TryGetValue(item2.ActorNumber, out value); WritePlayerReportSection(stringWriter, role, GetPlayerDisplayName(item2), item2.Succeeded, item2.Timestamp, item2.MatchedRequiredModCount, item2.RequiredModCount, item2.UnauthorizedCount, item2.ErrorMessage, value); } return stringWriter.ToString(); } private static void WritePlayerReportSection(StringWriter writer, string role, string playerDisplayName, bool succeeded, DateTime timestamp, int matchedRequiredModCount, int requiredModCount, int unauthorizedCount, string? errorMessage, IReadOnlyList? modDetails) { writer.WriteLine(); writer.WriteLine("[" + role + "] " + playerDisplayName); writer.WriteLine("Status: " + (succeeded ? "Passed" : "Failed")); writer.WriteLine("Verification Time: " + DisplayFormat.FormatDateTime(timestamp)); writer.WriteLine($"Required Mods: {matchedRequiredModCount}/{requiredModCount}"); writer.WriteLine($"Unauthorized Mods: {unauthorizedCount}"); if (!string.IsNullOrEmpty(errorMessage)) { writer.WriteLine("Error: " + errorMessage); } if (modDetails == null || modDetails.Count == 0) { return; } writer.WriteLine("Mods:"); foreach (ModVerificationDetail modDetail in modDetails) { writer.WriteLine("- " + modDetail.Mod.Name + " (" + modDetail.Mod.GUID + ") v" + modDetail.Mod.Version); writer.WriteLine(" SHA256: " + modDetail.Mod.SHA256); writer.WriteLine(" Status: " + FormatModStatus(modDetail.Status)); } } private static string FormatModStatus(ModVerificationStatus status) { return status switch { ModVerificationStatus.Allowed => "Allowed", ModVerificationStatus.AllowedExtra => "Allowed (Extra)", ModVerificationStatus.Missing => "Missing", ModVerificationStatus.Unauthorized => "Unauthorized", _ => status.ToString(), }; } private static bool IsHostPlayer(int actorNumber) { if (PhotonNetwork.MasterClient != null) { return PhotonNetwork.MasterClient.ActorNumber == actorNumber; } return false; } private List GetLoadedMods() { List list = new List(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { string location = value.Location; if ((string.IsNullOrEmpty(location) || !File.Exists(location)) && (Object)(object)value.Instance != (Object)null) { location = ((object)value.Instance).GetType().Assembly.Location; } ModMetadata metadata; if (string.IsNullOrEmpty(location) || !File.Exists(location)) { Logger.LogWarning((object)("Unable to locate plugin file for " + value.Metadata.GUID + ".")); } else if (!ModScanner.TryGetMetadata(location, value.Metadata.GUID, value.Metadata.Name, value.Metadata.Version.ToString(), out metadata)) { Logger.LogWarning((object)("Unable to retrieve metadata from " + location)); } else { list.Add(metadata); } } return list; } } internal static class GameRunState { internal static event Action? Changed; internal static void SetStarted(bool started) { GameRunState.Changed?.Invoke(started); } } [HarmonyPatch(typeof(RunManager), "StartRun")] internal static class RunManagerStartRunPatch { [HarmonyPostfix] private static void Postfix() { GameRunState.SetStarted(started: true); } } [HarmonyPatch(typeof(RunManager), "EndGame")] internal static class RunManagerEndGamePatch { [HarmonyPostfix] private static void Postfix() { GameRunState.SetStarted(started: false); } } [HarmonyPatch(typeof(Player), "LeaveCurrentGame")] internal static class PlayerLeaveCurrentGamePatch { [HarmonyPostfix] private static void Postfix() { GameRunState.SetStarted(started: false); } } public static class PluginConfig { internal static ConfigEntry EnableVerifier { get; private set; } internal static ConfigEntry ShowReport { get; private set; } internal static ConfigEntry KickUnverifiedPlayers { get; private set; } internal static ConfigEntry VerificationMode { get; private set; } internal static ConfigEntry ReportPos { get; private set; } internal static ConfigEntry ReportPaddingLeft { get; private set; } internal static ConfigEntry ReportPaddingBottom { get; private set; } internal static ConfigEntry RebuildWhitelistKey { get; private set; } public static void Initialize(ConfigFile config, ManualLogSource logger) { EnableVerifier = config.Bind("General", "EnableVerifier", true, "Check if players joining your room have the same mods installed."); ShowReport = config.Bind("General", "ShowReport", true, "Show the verification results."); KickUnverifiedPlayers = config.Bind("General", "KickUnverifiedPlayers", false, "Kick players who fail the verification."); VerificationMode = config.Bind("General", "VerificationMode", BetterModVerifier.VerificationMode.ExactMatch, "ExactMatch requires the player's mod set to match the whitelist. AllowExtra requires all whitelist mods but permits additional player mods."); ReportPos = config.Bind("UI", "ReportPosition", ReportPosition.Right, "The position of the verification report on the screen."); ReportPaddingLeft = config.Bind("UI", "ReportPaddingLeft", -20f, "The left padding of the verification report. Set to negative values to move left instead."); ReportPaddingBottom = config.Bind("UI", "ReportPaddingBottom", 0f, "The bottom padding of the verification report. Set to negative values to move down instead."); RebuildWhitelistKey = config.Bind("KeyBinds", "RebuildWhitelistKey", (KeyCode)285, "The key to rebuild the host's local whitelist file."); logger.LogInfo((object)"Plugin Config Loaded."); } } internal enum ReportPosition { Top, Bottom, Left, TopLeft, BottomLeft, Right, TopRight, BottomRight } internal class VerifierReportManager : MonoBehaviour { private TextMeshProUGUI report_textblock; private GameObject reportPanel; private DateTime timestamp; private bool hasSummary; private bool succeeded; private int matchedRequiredModCount; private int requiredModCount; private string errorMessage = string.Empty; private bool showClientSummary; private int passedClientCount; private int clientCount; private readonly List failedPlayerNames = new List(); private TMP_FontAsset? currentFont; private bool requestedVisible; public void Configure(ReportPosition position, Vector2 padding) { //IL_0047: 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_0061: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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) //IL_00c9: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) timestamp = DateTime.UtcNow; Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 1000; CanvasScaler val2 = ((Component)this).gameObject.AddComponent(); val2.uiScaleMode = (ScaleMode)1; val2.referenceResolution = new Vector2(1920f, 1080f); reportPanel = new GameObject("VerifierPanel"); reportPanel.transform.SetParent(((Component)this).gameObject.transform, false); reportPanel.AddComponent(); PlaceInPosition(reportPanel.GetComponent(), position, padding); VerticalLayoutGroup val3 = reportPanel.AddComponent(); ((HorizontalOrVerticalLayoutGroup)val3).spacing = 10f; ((LayoutGroup)val3).childAlignment = PositionToTextAnchor(position); GameObject val4 = new GameObject("VerifierReport"); val4.transform.SetParent(reportPanel.transform, false); report_textblock = val4.AddComponent(); ((TMP_Text)report_textblock).text = "Loading report..."; ((TMP_Text)report_textblock).richText = false; ((TMP_Text)report_textblock).alignment = PositionToAlignment(position); reportPanel.SetActive(false); } public void SetResult(VerificationResult result) { SetSummary(result.Report.Timestamp, result.Succeeded, result.MatchedRequiredModCount, result.RequiredModCount); } public void SetSummary(DateTime verificationTime, bool succeeded, int matchedRequiredModCount, int requiredModCount) { timestamp = verificationTime; this.succeeded = succeeded; this.matchedRequiredModCount = matchedRequiredModCount; this.requiredModCount = requiredModCount; errorMessage = string.Empty; hasSummary = true; UpdateReportText(); } public void SetReportError(string error) { errorMessage = error; hasSummary = false; UpdateReportText(); } public void SetClientSummary(int passedClientCount, int clientCount, IReadOnlyCollection failedPlayers) { this.passedClientCount = passedClientCount; this.clientCount = clientCount; failedPlayerNames.Clear(); failedPlayerNames.AddRange(failedPlayers); showClientSummary = true; UpdateReportText(); } public void ClearClientSummary() { if (showClientSummary) { showClientSummary = false; failedPlayerNames.Clear(); UpdateReportText(); } } public void SetVisible(bool visible) { requestedVisible = visible; ApplyVisibility(); } private void Update() { ApplyVisibility(); } private void UpdateReportText() { string text = (hasSummary ? ("Verification Time: " + DisplayFormat.FormatDateTime(timestamp) + "\r\n" + $"Required Mods: {matchedRequiredModCount}/{requiredModCount}\r\n" + "Status: " + (succeeded ? "Passed" : "Failed")) : (string.IsNullOrEmpty(errorMessage) ? "Loading report..." : ("Error during verification:\r\n" + errorMessage))); if (showClientSummary) { text += $"\r\n\r\nClients: {passedClientCount}/{clientCount} Passed"; if (failedPlayerNames.Count > 0) { text += "\r\nFailed Players:"; foreach (string failedPlayerName in failedPlayerNames) { text = text + "\r\n- [Failed] " + failedPlayerName; } } else { text += "\r\nFailed Players: None"; } } ((TMP_Text)report_textblock).text = text; } public static TextAnchor PositionToTextAnchor(ReportPosition position) { //IL_0029: 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_0031: 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_0039: 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_0041: 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_0049: Unknown result type (might be due to invalid IL or missing references) return (TextAnchor)(position switch { ReportPosition.Top => 1, ReportPosition.Bottom => 7, ReportPosition.Left => 3, ReportPosition.TopLeft => 0, ReportPosition.BottomLeft => 6, ReportPosition.Right => 5, ReportPosition.TopRight => 2, ReportPosition.BottomRight => 8, _ => 3, }); } public static TextAlignmentOptions PositionToAlignment(ReportPosition position) { //IL_002d: 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_003d: 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_0045: Unknown result type (might be due to invalid IL or missing references) switch (position) { case ReportPosition.Top: case ReportPosition.Bottom: return (TextAlignmentOptions)514; case ReportPosition.Left: case ReportPosition.TopLeft: case ReportPosition.BottomLeft: return (TextAlignmentOptions)513; case ReportPosition.Right: case ReportPosition.TopRight: case ReportPosition.BottomRight: return (TextAlignmentOptions)516; default: return (TextAlignmentOptions)513; } } private static void SetFont(TextMeshProUGUI textMesh, TMP_FontAsset font) { //IL_0021: 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_003c: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)textMesh).font = font; ((TMP_Text)textMesh).fontSize = 20f; ((TMP_Text)textMesh).overflowMode = (TextOverflowModes)0; ((TMP_Text)textMesh).textWrappingMode = (TextWrappingModes)0; ((Graphic)textMesh).color = Color.white; ((TMP_Text)textMesh).outlineWidth = 0.075f; ((TMP_Text)textMesh).outlineColor = Color32.op_Implicit(Color.black); } private void ApplyVisibility() { reportPanel.SetActive(requestedVisible && TryApplyGameFont()); } private bool TryApplyGameFont() { GUIManager instance = GUIManager.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.heroDayText == (Object)null || (Object)(object)((TMP_Text)instance.heroDayText).font == (Object)null) { return false; } TMP_FontAsset font = ((TMP_Text)instance.heroDayText).font; if ((Object)(object)currentFont != (Object)(object)font) { currentFont = font; SetFont(report_textblock, font); } return true; } private static void PlaceInPosition(RectTransform rect, ReportPosition pos, Vector2 padding) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_0089: 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) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: 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) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) Vector2 val = (rect.anchorMin = (Vector2)(pos switch { ReportPosition.TopLeft => new Vector2(0f, 1f), ReportPosition.Top => new Vector2(0.5f, 1f), ReportPosition.TopRight => new Vector2(1f, 1f), ReportPosition.Left => new Vector2(0f, 0.5f), ReportPosition.Right => new Vector2(1f, 0.5f), ReportPosition.BottomLeft => new Vector2(0f, 0f), ReportPosition.Bottom => new Vector2(0.5f, 0f), ReportPosition.BottomRight => new Vector2(1f, 0f), _ => new Vector2(0f, 1f), })); Vector2 val3 = (rect.anchorMax = val); Vector2 pivot = val3; rect.pivot = pivot; rect.anchoredPosition = padding; } } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ConstantExpectedAttribute : Attribute { public object? Min { get; set; } public object? Max { get; set; } } [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ExperimentalAttribute : Attribute { public string DiagnosticId { get; } public string? UrlFormat { get; set; } public ExperimentalAttribute(string diagnosticId) { DiagnosticId = diagnosticId; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullAttribute : Attribute { public string[] Members { get; } public MemberNotNullAttribute(string member) { Members = new string[1] { member }; } public MemberNotNullAttribute(params string[] members) { Members = members; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public string[] Members { get; } public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; Members = new string[1] { member }; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { ReturnValue = returnValue; Members = members; } } [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SetsRequiredMembersAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class StringSyntaxAttribute : Attribute { public const string CompositeFormat = "CompositeFormat"; public const string DateOnlyFormat = "DateOnlyFormat"; public const string DateTimeFormat = "DateTimeFormat"; public const string EnumFormat = "EnumFormat"; public const string GuidFormat = "GuidFormat"; public const string Json = "Json"; public const string NumericFormat = "NumericFormat"; public const string Regex = "Regex"; public const string TimeOnlyFormat = "TimeOnlyFormat"; public const string TimeSpanFormat = "TimeSpanFormat"; public const string Uri = "Uri"; public const string Xml = "Xml"; public string Syntax { get; } public object?[] Arguments { get; } public StringSyntaxAttribute(string syntax) { Syntax = syntax; Arguments = new object[0]; } public StringSyntaxAttribute(string syntax, params object?[] arguments) { Syntax = syntax; Arguments = arguments; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class UnscopedRefAttribute : Attribute { } } namespace System.Runtime.Versioning { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiresPreviewFeaturesAttribute : Attribute { public string? Message { get; } public string? Url { get; set; } public RequiresPreviewFeaturesAttribute() { } public RequiresPreviewFeaturesAttribute(string? message) { Message = message; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CallerArgumentExpressionAttribute : Attribute { public string ParameterName { get; } public CallerArgumentExpressionAttribute(string parameterName) { ParameterName = parameterName; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CollectionBuilderAttribute : Attribute { public Type BuilderType { get; } public string MethodName { get; } public CollectionBuilderAttribute(Type builderType, string methodName) { BuilderType = builderType; MethodName = methodName; } } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = "RefStructs"; public const string RequiredMembers = "RequiredMembers"; public string FeatureName { get; } public bool IsOptional { get; set; } public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public string[] Arguments { get; } public InterpolatedStringHandlerArgumentAttribute(string argument) { Arguments = new string[1] { argument }; } public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { Arguments = arguments; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerAttribute : Attribute { } [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal static class IsExternalInit { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ModuleInitializerAttribute : Attribute { } [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class OverloadResolutionPriorityAttribute : Attribute { public int Priority { get; } public OverloadResolutionPriorityAttribute(int priority) { Priority = priority; } } [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] [ExcludeFromCodeCoverage] internal sealed class ParamCollectionAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal sealed class RequiresLocationAttribute : Attribute { } [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SkipLocalsInitAttribute : Attribute { } }