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<ModMetadata> LoadedMods { get; }
public IReadOnlyList<ModVerificationDetail> 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<ModMetadata> loadedMods, IReadOnlyList<ModVerificationDetail> 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<ModMetadata> Mods = new List<ModMetadata>();
}
[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<int, PlayerVerificationData> Players = new Dictionary<int, PlayerVerificationData>();
}
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<ModMetadata> localMods = Array.Empty<ModMetadata>();
private string verifiedModsPath = string.Empty;
private ManualLogSource logger;
private PlayerConnectionLog playerConnectionLog;
private readonly Dictionary<int, PlayerVerificationData> results = new Dictionary<int, PlayerVerificationData>();
private readonly Dictionary<int, IReadOnlyList<ModVerificationDetail>> playerModDetails = new Dictionary<int, IReadOnlyList<ModVerificationDetail>>();
private readonly Dictionary<int, string> processedPlayerConfigs = new Dictionary<int, string>();
private readonly HashSet<int> pendingPlayers = new HashSet<int>();
private readonly HashSet<int> pendingKicks = new HashSet<int>();
private readonly HashSet<int> kickedPlayers = new HashSet<int>();
private bool initialized;
private bool deferResultsPublish;
internal event Action<RoomConfigData>? RoomConfigChanged;
internal event Action<IReadOnlyCollection<PlayerVerificationData>>? VerificationChanged;
internal event Action? JoinedRoom;
internal void Initialize(PhotonScopedManager photonManager, IReadOnlyList<ModMetadata> localMods, string whitelistPath, ManualLogSource logger)
{
this.photonManager = photonManager;
this.localMods = localMods;
verifiedModsPath = whitelistPath;
this.logger = logger;
photonManager.RegisterOnJoinedRoom((Action<Player>)OnJoinedRoomFromManager);
photonManager.RegisterRoomProperty<string>("BMV_ROOM_CFG", (RoomEventType)2, (Action<string>)OnRoomConfigReceived);
photonManager.RegisterRoomProperty<string>("BMV_ROOM_RESULTS", (RoomEventType)1, (Action<string>)OnRoomResultsReceived);
photonManager.RegisterPlayerProperty<string>("BMV_PLAYER_CFG", (PlayerEventType)2, (Action<Player, string>)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<RoomConfigData>(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<RoomVerificationData>(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<PlayerConfigData>(text);
if (playerConfigData.ProtocolVersion != 4)
{
throw new InvalidDataException($"Unsupported protocol version {playerConfigData.ProtocolVersion}.");
}
VerificationResult result = ModVerifier.VerifyPlayer(verifiedModsPath, playerConfigData.Mods ?? new List<ModMetadata>(), 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<Player, string> allPlayerProperties = photonManager.GetAllPlayerProperties<string>("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<int, IReadOnlyList<ModVerificationDetail>> GetPlayerModDetails()
{
return new Dictionary<int, IReadOnlyList<ModVerificationDetail>>(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 + "</color>");
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<PlayerConnectionLog>();
}
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<int, PlayerVerificationData>(results)
}));
}
catch (Exception ex)
{
logger.LogError((object)("Failed to publish verification results: " + ex.Message));
}
}
private static string Encode<T>(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<T>(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<T>(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<ModMetadata> loadedMods, VerificationMode mode, ManualLogSource logger)
{
IgnoredModEntry[] ignoredMods = LoadIgnoredMods(verifiedModsPath);
IReadOnlyList<ModMetadata> loadedMods2 = FilterIgnoredMods(loadedMods, ignoredMods);
EnsureWhitelistFile(verifiedModsPath, loadedMods2);
return VerifyLoadedMods(verifiedModsPath, loadedMods2, ignoredMods, mode, logger);
}
internal static VerificationResult VerifyPlayer(string verifiedModsPath, IReadOnlyList<ModMetadata> loadedMods, VerificationMode mode, ManualLogSource logger)
{
IgnoredModEntry[] ignoredMods = LoadIgnoredMods(verifiedModsPath);
IReadOnlyList<ModMetadata> loadedMods2 = FilterIgnoredMods(loadedMods, ignoredMods);
return VerifyLoadedMods(verifiedModsPath, loadedMods2, ignoredMods, mode, logger);
}
internal static int RebuildWhitelist(string verifiedModsPath, IReadOnlyList<ModMetadata> loadedMods)
{
IgnoredModEntry[] ignoredMods = LoadIgnoredMods(verifiedModsPath);
IReadOnlyList<ModMetadata> 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<ModMetadata> loadedMods, IReadOnlyList<IgnoredModEntry> ignoredMods, VerificationMode mode, ManualLogSource logger)
{
//IL_003f: Expected O, but got Unknown
DateTime utcNow = DateTime.UtcNow;
List<ModVerificationDetail> list = new List<ModVerificationDetail>();
int num = 0;
logger.LogDebug((object)("Fetching whitelist from " + verifiedModsPath));
ModMetadata[] array;
try
{
array = FilterIgnoredMods(LoadWhitelistFile(verifiedModsPath) ?? Array.Empty<ModMetadata>(), 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<ModMetadata> whitelist, IReadOnlyList<bool> 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<ModMetadata> 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<ModMetadata[]>(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<IgnoredModEntry>(), (Formatting)1));
return Array.Empty<IgnoredModEntry>();
}
try
{
return JsonConvert.DeserializeObject<IgnoredModEntry[]>(File.ReadAllText(ignoredModsPath)) ?? Array.Empty<IgnoredModEntry>();
}
catch (JsonException ex)
{
JsonException innerException = ex;
throw new Exception("Serializing the ignored mods: malformed ignored mod list", (Exception?)(object)innerException);
}
}
private static IReadOnlyList<ModMetadata> FilterIgnoredMods(IReadOnlyList<ModMetadata> mods, IReadOnlyList<IgnoredModEntry> ignoredMods)
{
if (ignoredMods.Count == 0)
{
return mods;
}
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (IgnoredModEntry ignoredMod in ignoredMods)
{
if (!string.IsNullOrWhiteSpace(ignoredMod.GUID))
{
hashSet.Add(ignoredMod.GUID);
}
}
if (hashSet.Count == 0)
{
return mods;
}
List<ModMetadata> list = new List<ModMetadata>();
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<ModMetadata> localMods = Array.Empty<ModMetadata>();
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<bool> 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<VerifierReportManager>();
reportManager.Configure(PluginConfig.ReportPos.Value, new Vector2(PluginConfig.ReportPaddingLeft.Value, PluginConfig.ReportPaddingBottom.Value));
lastOfflineMode = PhotonNetwork.OfflineMode;
RefreshReportVisibility();
modSyncManager = managerObject.AddComponent<ModSyncManager>();
}
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<PlayerVerificationData> results)
{
if (PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom)
{
return;
}
PlayerVerificationData playerVerificationData = null;
int num = 0;
int num2 = 0;
List<string> list = new List<string>();
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<PlayerVerificationData>? networkResults, IReadOnlyDictionary<int, IReadOnlyList<ModVerificationDetail>>? playerModDetails = null)
{
Logger.LogInfo((object)("Writing report into " + LogPath));
File.WriteAllText(LogPath, BuildReportText(localResult, networkResults, playerModDetails));
}
private static string BuildReportText(VerificationResult localResult, IReadOnlyCollection<PlayerVerificationData>? networkResults, IReadOnlyDictionary<int, IReadOnlyList<ModVerificationDetail>>? 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<PlayerVerificationData> list = new List<PlayerVerificationData>(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<ModVerificationDetail> 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<ModVerificationDetail>? 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<ModMetadata> GetLoadedMods()
{
List<ModMetadata> list = new List<ModMetadata>();
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<bool>? 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<bool> EnableVerifier { get; private set; }
internal static ConfigEntry<bool> ShowReport { get; private set; }
internal static ConfigEntry<bool> KickUnverifiedPlayers { get; private set; }
internal static ConfigEntry<VerificationMode> VerificationMode { get; private set; }
internal static ConfigEntry<ReportPosition> ReportPos { get; private set; }
internal static ConfigEntry<float> ReportPaddingLeft { get; private set; }
internal static ConfigEntry<float> ReportPaddingBottom { get; private set; }
internal static ConfigEntry<KeyCode> RebuildWhitelistKey { get; private set; }
public static void Initialize(ConfigFile config, ManualLogSource logger)
{
EnableVerifier = config.Bind<bool>("General", "EnableVerifier", true, "Check if players joining your room have the same mods installed.");
ShowReport = config.Bind<bool>("General", "ShowReport", true, "Show the verification results.");
KickUnverifiedPlayers = config.Bind<bool>("General", "KickUnverifiedPlayers", false, "Kick players who fail the verification.");
VerificationMode = config.Bind<VerificationMode>("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<ReportPosition>("UI", "ReportPosition", ReportPosition.Right, "The position of the verification report on the screen.");
ReportPaddingLeft = config.Bind<float>("UI", "ReportPaddingLeft", -20f, "The left padding of the verification report. Set to negative values to move left instead.");
ReportPaddingBottom = config.Bind<float>("UI", "ReportPaddingBottom", 0f, "The bottom padding of the verification report. Set to negative values to move down instead.");
RebuildWhitelistKey = config.Bind<KeyCode>("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<string> failedPlayerNames = new List<string>();
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<Canvas>();
val.renderMode = (RenderMode)0;
val.sortingOrder = 1000;
CanvasScaler val2 = ((Component)this).gameObject.AddComponent<CanvasScaler>();
val2.uiScaleMode = (ScaleMode)1;
val2.referenceResolution = new Vector2(1920f, 1080f);
reportPanel = new GameObject("VerifierPanel");
reportPanel.transform.SetParent(((Component)this).gameObject.transform, false);
reportPanel.AddComponent<RectTransform>();
PlaceInPosition(reportPanel.GetComponent<RectTransform>(), position, padding);
VerticalLayoutGroup val3 = reportPanel.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val3).spacing = 10f;
((LayoutGroup)val3).childAlignment = PositionToTextAnchor(position);
GameObject val4 = new GameObject("VerifierReport");
val4.transform.SetParent(reportPanel.transform, false);
report_textblock = val4.AddComponent<TextMeshProUGUI>();
((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<string> 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
{
}
}