using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; 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.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Jotunn.GUI; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using ServerSync; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.TextCore; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Clan")] [assembly: AssemblyDescription("Server-authoritative Valheim clan system")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("Clan")] [assembly: AssemblyCopyright("Copyright © sighsorry 2026")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } 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 Clan { public enum ClanRole { Leader, Officer, Member, Guest } internal enum ClanRequestType { RequestSnapshot, CreateClan, Invite, Apply, AcceptInvite, DeclineInvite, AcceptApplication, RejectApplication, KickPlayer, TransferLeadership, SetRole, LeaveClan, SendClanChat, SendClanPing, UpdatePosition, CancelApplication, UpdateClanProfile, RequestDirectory, RenameClan, RequestHud } internal enum ClanOperationResultCode { None, Success, Unchanged, InvalidName, IdentityUnavailable, Unauthorized, ClanChanged, NameTaken, RateLimited, Unavailable, Failed, SnapshotRateLimited, IdentityRejected } internal enum ClanResponseType { Snapshot, Chat, MapPing, PositionUpdate, Directory, DirectoryInvalidated, Hud } internal enum ClanDirectoryPlayerState { None, Clan, Pending, Invited } internal enum ClanValidationField { None, ClanName, ClanDescription, ClanEmblemKey } internal enum ClanValidationError { Required, TooLong, RichText, ControlCharacters, InvalidUnicode, InvalidCharacters, TrailingSeparator, MissingLetterOrNumber, InvalidStartOrEnd } internal static class ClanDataRules { public const int MaxPlatformIdLength = 128; public const int MaxPlayerKeyLength = 160; public const int MaxPlayerNameLength = 64; public const int ClanIdLength = 32; public const int MaxClanNameLength = 40; public const int MaxClanDescriptionLength = 160; public const int MaxClanEmblemKeyLength = 32; public const int MaxChatMessageLength = 400; public const int MaxStatusLength = 1024; public const int MaxInviteIdLength = 64; public const int MaxClans = 1024; public const int MaxMembersPerClan = 512; public const int MaxApplicationsPerClan = 512; public const int MaxInvites = 4096; public const int MaxDirectoryPlayers = 1024; public const int MaxHudPlayers = 10; public const float MaxHudHealth = 1000000f; private static readonly object ValidationErrorDataKey = new object(); public static string RequireText(string? value, int maxLength, string fieldName, bool allowEmpty = true, bool allowLineBreaks = false, ClanValidationField validationField = ClanValidationField.None) { string text = (value ?? "").Trim(); if (!allowEmpty && text.Length == 0) { throw ValidationFailure(validationField, ClanValidationError.Required, fieldName + " is required."); } if (text.Length > maxLength) { throw ValidationFailure(validationField, ClanValidationError.TooLong, $"{fieldName} exceeds {maxLength} characters."); } if (text.IndexOf('<') >= 0 || text.IndexOf('>') >= 0) { throw ValidationFailure(validationField, ClanValidationError.RichText, fieldName + " cannot contain rich-text tags."); } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) && (!allowLineBreaks || (c != '\r' && c != '\n' && c != '\t'))) { throw ValidationFailure(validationField, ClanValidationError.ControlCharacters, fieldName + " contains unsupported control characters."); } } return text; } public static string RequireClanDescription(string? value, string fieldName = "clan description") { return RequireText(value, 160, fieldName, allowEmpty: true, allowLineBreaks: false, ClanValidationField.ClanDescription); } public static string ReadClanDescription(ZPackage package, string fieldName = "clan description") { return RequireClanDescription(package.ReadString(), fieldName); } public static void WriteClanDescription(ZPackage package, string? value, string fieldName = "clan description") { package.Write(RequireClanDescription(value, fieldName)); } public static string RequireClanName(string? value, string fieldName = "clan name") { string text; try { text = (value ?? "").Normalize(NormalizationForm.FormC); } catch (ArgumentException innerException) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidUnicode, fieldName + " contains invalid Unicode data.", innerException); } StringBuilder stringBuilder = new StringBuilder(text.Length); bool flag = false; string text2 = text; foreach (char c in text2) { if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator) { if (stringBuilder.Length > 0 && !flag) { stringBuilder.Append(' '); } flag = true; } else { stringBuilder.Append(c); flag = false; } } string text3 = stringBuilder.ToString().Trim(new char[1] { ' ' }); if (text3.Length == 0) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.Required, fieldName + " is required."); } int num = 0; bool flag2 = false; bool flag3 = true; bool flag4 = false; int num2; for (int j = 0; j < text3.Length; j += num2) { char c2 = text3[j]; num2 = 1; if (char.IsHighSurrogate(c2)) { if (j + 1 >= text3.Length || !char.IsLowSurrogate(text3[j + 1])) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidUnicode, fieldName + " contains invalid Unicode data."); } num2 = 2; } else if (char.IsLowSurrogate(c2)) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidUnicode, fieldName + " contains invalid Unicode data."); } UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(text3, j); bool flag5 = (uint)unicodeCategory <= 4u; bool flag6 = flag5; flag5 = (uint)(unicodeCategory - 8) <= 2u; bool flag7 = flag5; flag5 = (uint)(unicodeCategory - 5) <= 2u; bool flag8 = flag5; bool flag9 = num2 == 1 && (c2 == ' ' || c2 == '-' || c2 == '_' || c2 == '·'); if (flag6 || flag7) { flag2 = true; flag3 = false; flag4 = true; } else if (flag8 && flag4) { flag3 = false; } else { if (!flag9 || flag3) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidCharacters, fieldName + " can contain only letters, numbers, marks, spaces, '-', '_', or '·'."); } flag3 = true; flag4 = false; } num++; if (num > 40) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.TooLong, $"{fieldName} exceeds {40} Unicode characters."); } } if (flag3) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.TrailingSeparator, fieldName + " cannot end with a separator."); } if (!flag2) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.MissingLetterOrNumber, fieldName + " must contain a letter or number."); } return text3; } public static string RequireOptionalClanName(string? value, string fieldName = "clan name") { if (!string.IsNullOrWhiteSpace(value)) { return RequireClanName(value, fieldName); } return ""; } public static string ReadClanName(ZPackage package, string fieldName = "clan name") { return RequireClanName(package.ReadString(), fieldName); } public static string ReadOptionalClanName(ZPackage package, string fieldName = "clan name") { return RequireOptionalClanName(package.ReadString(), fieldName); } public static void WriteClanName(ZPackage package, string? value, string fieldName = "clan name") { package.Write(RequireClanName(value, fieldName)); } public static void WriteOptionalClanName(ZPackage package, string? value, string fieldName = "clan name") { package.Write(RequireOptionalClanName(value, fieldName)); } public static string ReadText(ZPackage package, int maxLength, string fieldName, bool allowEmpty = true, bool allowLineBreaks = false) { return RequireText(package.ReadString(), maxLength, fieldName, allowEmpty, allowLineBreaks); } public static void WriteText(ZPackage package, string? value, int maxLength, string fieldName, bool allowEmpty = true, bool allowLineBreaks = false) { package.Write(RequireText(value, maxLength, fieldName, allowEmpty, allowLineBreaks)); } public static string RequirePlayerKey(string? value, string fieldName = "player key") { string text = RequireText(value, 160, fieldName, allowEmpty: false); if (!TryParsePlayerKey(text, out string _, out long _)) { throw new InvalidDataException(fieldName + " is invalid."); } return text; } public static string ReadPlayerKey(ZPackage package, string fieldName = "player key") { return RequirePlayerKey(package.ReadString(), fieldName); } public static void WritePlayerKey(ZPackage package, string? value, string fieldName = "player key") { package.Write(RequirePlayerKey(value, fieldName)); } public static string RequirePlatformId(string? value, string fieldName = "platform id") { return RequireText(value, 128, fieldName, allowEmpty: false); } public static long RequireCharacterPlayerId(long value, string fieldName = "character player id") { if (value == 0L) { throw new InvalidDataException(fieldName + " must be non-zero."); } return value; } public static string BuildPlayerKey(string? platformId, long playerId) { string text = RequirePlatformId(platformId); long num = RequireCharacterPlayerId(playerId); return text.Length.ToString(CultureInfo.InvariantCulture) + ":" + text + ":" + num.ToString(CultureInfo.InvariantCulture); } public static bool TryParsePlayerKey(string? value, out string platformId, out long playerId) { platformId = ""; playerId = 0L; string text = value ?? ""; int num = text.IndexOf(':'); if (num <= 0 || !int.TryParse(text.Substring(0, num), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0 || result > 128) { return false; } int num2 = num + 1; int num3 = num2 + result; if (num3 >= text.Length || text[num3] != ':') { return false; } string text2 = text.Substring(num2, result); if (!long.TryParse(text.Substring(num3 + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 == 0L || NormalizePlatformId(text2).Length == 0) { return false; } string x; try { x = BuildPlayerKey(text2, result2); } catch (InvalidDataException) { return false; } if (!StringComparer.Ordinal.Equals(x, text)) { return false; } platformId = text2; playerId = result2; return true; } public static string ReadPlayerName(ZPackage package, string fieldName = "player name") { return ReadText(package, 64, fieldName); } public static void WritePlayerName(ZPackage package, string? value, string fieldName = "player name") { WriteText(package, value, 64, fieldName); } public static string RequireClanId(string? value, string fieldName = "clan id") { string text = RequireText(value, 32, fieldName, allowEmpty: false); if (text.Length != 32 || !Guid.TryParseExact(text, "N", out var result) || result == Guid.Empty || !StringComparer.Ordinal.Equals(result.ToString("N"), text)) { throw new InvalidDataException(fieldName + " is invalid."); } return text; } public static string RequireOptionalClanId(string? value, string fieldName = "clan id") { if (!string.IsNullOrWhiteSpace(value)) { return RequireClanId(value, fieldName); } return ""; } public static string ReadClanId(ZPackage package, string fieldName = "clan id") { return RequireClanId(package.ReadString(), fieldName); } public static string ReadOptionalClanId(ZPackage package, string fieldName = "clan id") { return RequireOptionalClanId(package.ReadString(), fieldName); } public static void WriteClanId(ZPackage package, string? value, string fieldName = "clan id") { package.Write(RequireClanId(value, fieldName)); } public static void WriteOptionalClanId(ZPackage package, string? value, string fieldName = "clan id") { package.Write(RequireOptionalClanId(value, fieldName)); } public static string RequireClanEmblemKey(string? value, string fieldName = "clan emblem key") { string text = RequireText(value, 32, fieldName, allowEmpty: true, allowLineBreaks: false, ClanValidationField.ClanEmblemKey); if (text.Length == 0) { return text; } if (!IsLowerAsciiLetterOrDigit(text[0]) || !IsLowerAsciiLetterOrDigit(text[text.Length - 1])) { throw ValidationFailure(ClanValidationField.ClanEmblemKey, ClanValidationError.InvalidStartOrEnd, fieldName + " must start and end with a lowercase ASCII letter or digit."); } string text2 = text; foreach (char c in text2) { if (!IsLowerAsciiLetterOrDigit(c) && c != '_' && c != '-') { throw ValidationFailure(ClanValidationField.ClanEmblemKey, ClanValidationError.InvalidCharacters, fieldName + " can contain only lowercase ASCII letters, digits, '_' or '-'."); } } return text; } public static string ReadClanEmblemKey(ZPackage package, string fieldName = "clan emblem key") { return RequireClanEmblemKey(package.ReadString(), fieldName); } public static void WriteClanEmblemKey(ZPackage package, string? value, string fieldName = "clan emblem key") { package.Write(RequireClanEmblemKey(value, fieldName)); } public static T RequireEnum(T value, string fieldName) where T : struct, Enum { if (!Enum.IsDefined(typeof(T), value)) { throw new InvalidDataException($"{fieldName} has an unknown value ({Convert.ToInt32(value)})."); } return value; } public static T ReadEnum(ZPackage package, string fieldName) where T : struct, Enum { int num = package.ReadInt(); if (!Enum.IsDefined(typeof(T), num)) { throw new InvalidDataException($"{fieldName} has an unknown value ({num})."); } return (T)Enum.ToObject(typeof(T), num); } public static ClanRole RequireAssignableRole(ClanRole role, string fieldName) { RequireEnum(role, fieldName); if (role != ClanRole.Officer && role != ClanRole.Member && role != ClanRole.Guest) { throw new InvalidDataException(fieldName + " must be Officer, Member, or Guest."); } return role; } public static ClanRole ReadAssignableRole(ZPackage package, string fieldName) { return RequireAssignableRole(ReadEnum(package, fieldName), fieldName); } public static int GetRolePower(ClanRole role) { return RequireEnum(role, "clan role") switch { ClanRole.Leader => 3, ClanRole.Officer => 2, ClanRole.Member => 1, ClanRole.Guest => 0, _ => throw new InvalidDataException("Clan role is unsupported."), }; } public static int RequireRange(int value, int minimum, int maximum, string fieldName) { if (value < minimum || value > maximum) { throw new InvalidDataException(fieldName + " is outside the supported range."); } return value; } public static long RequireRequestId(long value, string fieldName = "request id") { if (value <= 0) { throw new InvalidDataException(fieldName + " must be positive."); } return value; } public static long RequireOptionalRequestId(long value, string fieldName = "response request id") { if (value < 0) { throw new InvalidDataException(fieldName + " cannot be negative."); } return value; } public static int ReadCount(ZPackage package, int maximum, string fieldName) { return RequireRange(package.ReadInt(), 0, maximum, fieldName + " count"); } public static void RequireCount(int count, int maximum, string fieldName) { RequireRange(count, 0, maximum, fieldName + " count"); } public static Vector3 ReadFiniteVector(ZPackage package, string fieldName) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Vector3 val = package.ReadVector3(); RequireFiniteVector(val, fieldName); return val; } public static void RequireFiniteVector(Vector3 value, string fieldName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!IsFinite(value.x) || !IsFinite(value.y) || !IsFinite(value.z)) { throw new InvalidDataException(fieldName + " must contain finite coordinates."); } } public static string NormalizePlayerKey(string? value) { try { return RequirePlayerKey(value); } catch (InvalidDataException) { return ""; } } public static string NormalizePlatformId(string? value) { try { return RequirePlatformId(value); } catch (InvalidDataException) { return ""; } } public static string SanitizePlayerName(string? value) { string text = (value ?? "").Replace("<", " ").Replace(">", " "); StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 64)); for (int i = 0; i < text.Length; i++) { if (stringBuilder.Length >= 64) { break; } char c = text[i]; if (char.IsControl(c)) { continue; } bool flag = char.IsHighSurrogate(c) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1]); if (!char.IsSurrogate(c) || flag) { int num = ((!flag) ? 1 : 2); if (stringBuilder.Length > 64 - num) { break; } stringBuilder.Append(c); if (flag) { stringBuilder.Append(text[++i]); } } } return stringBuilder.ToString().Trim(); } public static bool TryGetValidationError(InvalidDataException exception, out ClanValidationField field, out ClanValidationError error) { if (exception.Data[ValidationErrorDataKey] is (ClanValidationField, ClanValidationError) tuple) { (field, error) = tuple; return true; } field = ClanValidationField.None; error = ClanValidationError.Required; return false; } private static InvalidDataException ValidationFailure(ClanValidationField field, ClanValidationError error, string message, Exception? innerException = null) { InvalidDataException ex = new InvalidDataException(message, innerException); if (field != ClanValidationField.None) { ex.Data[ValidationErrorDataKey] = (field, error); } return ex; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool IsLowerAsciiLetterOrDigit(char value) { switch (value) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return true; default: return false; } } } internal readonly struct ClanPlayerRef : IEquatable { public readonly string Id; public readonly string PlatformId; public readonly long CharacterPlayerId; public readonly string Name; public bool IsValid { get { if (!string.IsNullOrWhiteSpace(PlatformId) && CharacterPlayerId != 0L) { return !string.IsNullOrWhiteSpace(Id); } return false; } } public ClanPlayerRef(string? platformId, long playerId, string? name) { PlatformId = ClanDataRules.NormalizePlatformId(platformId); CharacterPlayerId = playerId; Name = ClanDataRules.SanitizePlayerName(name); Id = ((PlatformId.Length == 0 || CharacterPlayerId == 0L) ? "" : ClanDataRules.BuildPlayerKey(PlatformId, CharacterPlayerId)); } public static ClanPlayerRef Local() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) IDistributionPlatform distributionPlatform = PlatformManager.DistributionPlatform; string platformId = ((((distributionPlatform != null) ? distributionPlatform.LocalUser : null) != null) ? ((object)((IUser)distributionPlatform.LocalUser).PlatformUserID/*cast due to .constrained prefix*/).ToString() : ""); Player localPlayer = Player.m_localPlayer; long num2; if (localPlayer == null) { Game instance = Game.instance; long? obj; if (instance == null) { obj = null; } else { PlayerProfile playerProfile = instance.GetPlayerProfile(); obj = ((playerProfile != null) ? new long?(playerProfile.GetPlayerID()) : ((long?)null)); } long? num = obj; num2 = num.GetValueOrDefault(); } else { num2 = localPlayer.GetPlayerID(); } long playerId = num2; Game instance2 = Game.instance; object obj2; if (instance2 == null) { obj2 = null; } else { PlayerProfile playerProfile2 = instance2.GetPlayerProfile(); obj2 = ((playerProfile2 != null) ? playerProfile2.GetName() : null); } if (obj2 == null) { obj2 = "Unknown"; } string name = (string)obj2; return new ClanPlayerRef(platformId, playerId, name); } public void Write(ZPackage package) { ClanDataRules.WriteText(package, PlatformId, 128, "platform id", allowEmpty: false); package.Write(ClanDataRules.RequireCharacterPlayerId(CharacterPlayerId)); ClanDataRules.WritePlayerName(package, Name); } public static ClanPlayerRef Read(ZPackage package) { return new ClanPlayerRef(ClanDataRules.ReadText(package, 128, "platform id", allowEmpty: false), ClanDataRules.RequireCharacterPlayerId(package.ReadLong()), ClanDataRules.ReadPlayerName(package)); } public bool Equals(ClanPlayerRef other) { return StringComparer.Ordinal.Equals(Id, other.Id); } public override bool Equals(object? obj) { if (obj is ClanPlayerRef other) { return Equals(other); } return false; } public override int GetHashCode() { if (Id != null) { return StringComparer.Ordinal.GetHashCode(Id); } return 0; } public override string ToString() { string text = (IsValid ? $"{PlatformId}/{CharacterPlayerId}" : ""); if (!string.IsNullOrWhiteSpace(Name)) { return Name + " (" + text + ")"; } return text; } public static bool operator ==(ClanPlayerRef left, ClanPlayerRef right) { return left.Equals(right); } public static bool operator !=(ClanPlayerRef left, ClanPlayerRef right) { return !left.Equals(right); } } internal sealed class ClanMember { public ClanPlayerRef Player; public ClanRole Role = ClanRole.Member; public float LastClanChatTime = float.NegativeInfinity; public float LastClanPingTime = float.NegativeInfinity; } internal sealed class ClanInvite { public string InviteId = Guid.NewGuid().ToString("N"); public string ClanId = ""; public string FromName = ""; public ClanPlayerRef Target; } internal sealed class ClanState { public long CreationOrder; public string Name = ""; public string Description = ""; public string EmblemKey = ""; public readonly Dictionary Members = new Dictionary(StringComparer.Ordinal); public readonly Dictionary Applications = new Dictionary(StringComparer.Ordinal); public string ClanId { get; } public ClanState(string clanId) { ClanId = ClanDataRules.RequireClanId(clanId); } public bool IsLeader(ClanPlayerRef player) { if (player.IsValid && Members.TryGetValue(player.Id, out ClanMember value)) { return value.Role == ClanRole.Leader; } return false; } } internal sealed class ClanRequest { public ClanRequestType Type; public string ClanId = ""; public string ClanName = ""; public string TargetId = ""; public string InviteId = ""; public string Message = ""; public string Description = ""; public string EmblemKey = ""; public ClanRole Role = ClanRole.Member; public long RequestId; public long HudSelectionRevision; public long HudStateRevision; public Vector3 Position = Vector3.zero; public static ClanRequest Simple(ClanRequestType type) { return new ClanRequest { Type = type }; } public void Write(ZPackage package) { //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) ClanDataRules.RequireEnum(Type, "clan request type"); package.Write((int)Type); switch (Type) { case ClanRequestType.RequestSnapshot: case ClanRequestType.CancelApplication: break; case ClanRequestType.LeaveClan: ClanDataRules.WriteClanId(package, ClanId); break; case ClanRequestType.RequestHud: ClanDataRules.WriteClanId(package, ClanId); package.Write(ClanDataRules.RequireOptionalRequestId(HudSelectionRevision, "HUD selection revision")); package.Write(ClanDataRules.RequireOptionalRequestId(HudStateRevision, "HUD state revision")); break; case ClanRequestType.RequestDirectory: package.Write(ClanDataRules.RequireRequestId(RequestId)); break; case ClanRequestType.CreateClan: package.Write(ClanDataRules.RequireRequestId(RequestId)); ClanDataRules.WriteClanName(package, ClanName); ClanDataRules.WriteClanDescription(package, Description); ClanDataRules.WriteClanEmblemKey(package, EmblemKey); break; case ClanRequestType.Invite: case ClanRequestType.AcceptApplication: case ClanRequestType.RejectApplication: case ClanRequestType.KickPlayer: case ClanRequestType.TransferLeadership: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WritePlayerKey(package, TargetId, "target player key"); break; case ClanRequestType.Apply: ClanDataRules.WriteClanId(package, ClanId); break; case ClanRequestType.AcceptInvite: case ClanRequestType.DeclineInvite: ClanDataRules.WriteText(package, InviteId, 64, "invite id", allowEmpty: false); break; case ClanRequestType.SetRole: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WritePlayerKey(package, TargetId, "target player key"); package.Write((int)ClanDataRules.RequireAssignableRole(Role, "clan role")); break; case ClanRequestType.UpdateClanProfile: package.Write(ClanDataRules.RequireRequestId(RequestId)); ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, ClanName); ClanDataRules.WriteClanDescription(package, Description); ClanDataRules.WriteClanEmblemKey(package, EmblemKey); break; case ClanRequestType.RenameClan: package.Write(ClanDataRules.RequireRequestId(RequestId)); ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, ClanName); break; case ClanRequestType.SendClanChat: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteText(package, Message, 400, "clan chat message", allowEmpty: false); break; case ClanRequestType.SendClanPing: case ClanRequestType.UpdatePosition: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.RequireFiniteVector(Position, "position"); package.Write(Position); break; default: throw new InvalidDataException($"Unknown clan request type ({(int)Type})."); } } public static ClanRequest Read(ZPackage package) { //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) ClanRequest clanRequest = new ClanRequest { Type = ClanDataRules.ReadEnum(package, "clan request type") }; switch (clanRequest.Type) { case ClanRequestType.LeaveClan: clanRequest.ClanId = ClanDataRules.ReadClanId(package); break; case ClanRequestType.RequestHud: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.HudSelectionRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD selection revision"); clanRequest.HudStateRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD state revision"); break; case ClanRequestType.RequestDirectory: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); break; case ClanRequestType.CreateClan: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); clanRequest.ClanName = ClanDataRules.ReadClanName(package); clanRequest.Description = ClanDataRules.ReadClanDescription(package); clanRequest.EmblemKey = ClanDataRules.ReadClanEmblemKey(package); break; case ClanRequestType.Invite: case ClanRequestType.AcceptApplication: case ClanRequestType.RejectApplication: case ClanRequestType.KickPlayer: case ClanRequestType.TransferLeadership: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.TargetId = ClanDataRules.ReadPlayerKey(package, "target player key"); break; case ClanRequestType.Apply: clanRequest.ClanId = ClanDataRules.ReadClanId(package); break; case ClanRequestType.AcceptInvite: case ClanRequestType.DeclineInvite: clanRequest.InviteId = ClanDataRules.ReadText(package, 64, "invite id", allowEmpty: false); break; case ClanRequestType.SetRole: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.TargetId = ClanDataRules.ReadPlayerKey(package, "target player key"); clanRequest.Role = ClanDataRules.ReadAssignableRole(package, "clan role"); break; case ClanRequestType.UpdateClanProfile: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.ClanName = ClanDataRules.ReadClanName(package); clanRequest.Description = ClanDataRules.ReadClanDescription(package); clanRequest.EmblemKey = ClanDataRules.ReadClanEmblemKey(package); break; case ClanRequestType.RenameClan: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.ClanName = ClanDataRules.ReadClanName(package); break; case ClanRequestType.SendClanChat: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.Message = ClanDataRules.ReadText(package, 400, "clan chat message", allowEmpty: false); break; case ClanRequestType.SendClanPing: case ClanRequestType.UpdatePosition: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.Position = ClanDataRules.ReadFiniteVector(package, "position"); break; } if (package.GetPos() != package.Size()) { throw new InvalidDataException("Clan request contains unexpected trailing data."); } return clanRequest; } } internal sealed class ClanPlayerSummary { public ClanPlayerRef Player; public ClanRole Role = ClanRole.Member; public bool IsSelf; public bool IsOnline; public string Id => Player.Id; public string Name => Player.Name; public void Write(ZPackage package) { if (!Player.IsValid) { throw new InvalidDataException("roster player identity is invalid."); } Player.Write(package); package.Write((int)ClanDataRules.RequireEnum(Role, "clan role")); package.Write(IsSelf); package.Write(IsOnline); } public static ClanPlayerSummary Read(ZPackage package) { return new ClanPlayerSummary { Player = ClanPlayerRef.Read(package), Role = ClanDataRules.ReadEnum(package, "clan role"), IsSelf = package.ReadBool(), IsOnline = package.ReadBool() }; } } internal sealed class ClanHudPlayerSummary { public string PlayerId = ""; public bool HasHealth; public float CurrentHealth; public float MaxHealth; public void Write(ZPackage package) { ClanDataRules.WritePlayerKey(package, PlayerId, "HUD player key"); package.Write(HasHealth); if (HasHealth) { ValidateHealth(CurrentHealth, MaxHealth); package.Write(CurrentHealth); package.Write(MaxHealth); } } public static ClanHudPlayerSummary Read(ZPackage package) { ClanHudPlayerSummary clanHudPlayerSummary = new ClanHudPlayerSummary { PlayerId = ClanDataRules.ReadPlayerKey(package, "HUD player key"), HasHealth = package.ReadBool() }; if (clanHudPlayerSummary.HasHealth) { clanHudPlayerSummary.CurrentHealth = package.ReadSingle(); clanHudPlayerSummary.MaxHealth = package.ReadSingle(); ValidateHealth(clanHudPlayerSummary.CurrentHealth, clanHudPlayerSummary.MaxHealth); } return clanHudPlayerSummary; } private static void ValidateHealth(float currentHealth, float maxHealth) { if (float.IsNaN(currentHealth) || float.IsInfinity(currentHealth) || float.IsNaN(maxHealth) || float.IsInfinity(maxHealth) || maxHealth <= 0f || maxHealth > 1000000f || currentHealth < 0f || currentHealth > maxHealth) { throw new InvalidDataException("HUD health is outside the supported range."); } } } internal sealed class ClanHudSnapshot { public string ClanId = ""; public long SelectionRevision; public long StateRevision; public bool ReplaceSelection; public readonly List Players = new List(); public void Write(ZPackage package) { ClanDataRules.WriteOptionalClanId(package, ClanId, "HUD clan id"); package.Write(ClanDataRules.RequireOptionalRequestId(SelectionRevision, "HUD selection revision")); package.Write(ClanDataRules.RequireOptionalRequestId(StateRevision, "HUD state revision")); package.Write(ReplaceSelection); ClanDataRules.RequireCount(Players.Count, 10, "HUD player"); package.Write(Players.Count); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ClanHudPlayerSummary player in Players) { if (!hashSet.Add(player.PlayerId)) { throw new InvalidDataException("HUD snapshot contains a duplicate player."); } player.Write(package); } } public static ClanHudSnapshot Read(ZPackage package) { ClanHudSnapshot clanHudSnapshot = new ClanHudSnapshot { ClanId = ClanDataRules.ReadOptionalClanId(package, "HUD clan id"), SelectionRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD selection revision"), StateRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD state revision"), ReplaceSelection = package.ReadBool() }; int num = ClanDataRules.ReadCount(package, 10, "HUD player"); HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < num; i++) { ClanHudPlayerSummary clanHudPlayerSummary = ClanHudPlayerSummary.Read(package); if (!hashSet.Add(clanHudPlayerSummary.PlayerId)) { throw new InvalidDataException("HUD snapshot contains a duplicate player."); } clanHudSnapshot.Players.Add(clanHudPlayerSummary); } return clanHudSnapshot; } } internal sealed class ClanPublicSummary { public string ClanId = ""; public string Name = ""; public string Description = ""; public string EmblemKey = ""; public string LeaderName = ""; public int MemberCount; public void Write(ZPackage package) { ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, Name); ClanDataRules.WriteClanDescription(package, Description); ClanDataRules.WriteClanEmblemKey(package, EmblemKey); ClanDataRules.WritePlayerName(package, LeaderName, "clan leader name"); package.Write(ClanDataRules.RequireRange(MemberCount, 0, 512, "clan member count")); } public static ClanPublicSummary Read(ZPackage package) { return new ClanPublicSummary { ClanId = ClanDataRules.ReadClanId(package), Name = ClanDataRules.ReadClanName(package), Description = ClanDataRules.ReadClanDescription(package), EmblemKey = ClanDataRules.ReadClanEmblemKey(package), LeaderName = ClanDataRules.ReadPlayerName(package, "clan leader name"), MemberCount = ClanDataRules.RequireRange(package.ReadInt(), 0, 512, "clan member count") }; } } internal sealed class ClanApplicationSummary { public string PlayerId = ""; public string PlayerName = ""; public void Write(ZPackage package) { ClanDataRules.WritePlayerKey(package, PlayerId, "applicant key"); ClanDataRules.WritePlayerName(package, PlayerName, "applicant name"); } public static ClanApplicationSummary Read(ZPackage package) { return new ClanApplicationSummary { PlayerId = ClanDataRules.ReadPlayerKey(package, "applicant key"), PlayerName = ClanDataRules.ReadPlayerName(package, "applicant name") }; } } internal sealed class ClanInviteSummary { public string InviteId = ""; public string ClanId = ""; public string ClanName = ""; public string FromName = ""; public void Write(ZPackage package) { ClanDataRules.WriteText(package, InviteId, 64, "invite id", allowEmpty: false); ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, ClanName); ClanDataRules.WritePlayerName(package, FromName, "inviter name"); } public static ClanInviteSummary Read(ZPackage package) { return new ClanInviteSummary { InviteId = ClanDataRules.ReadText(package, 64, "invite id", allowEmpty: false), ClanId = ClanDataRules.ReadClanId(package), ClanName = ClanDataRules.ReadClanName(package), FromName = ClanDataRules.ReadPlayerName(package, "inviter name") }; } } internal sealed class ClanClientSnapshot { public string Status = ""; public long ResponseRequestId; public ClanOperationResultCode ResponseResultCode; public string ClanId = ""; public string ClanName = ""; public string ClanDescription = ""; public string ClanEmblemKey = ""; public ClanRole SelfRole = ClanRole.Member; public string PrimaryClanId = ""; public string PrimaryClanName = ""; public ClanRole PrimaryRole = ClanRole.Member; public string GuestClanId = ""; public string GuestClanName = ""; public readonly List Roster = new List(); public readonly List Applications = new List(); public ClanInviteSummary? Invite; public string OwnApplicationClanId = ""; public string OwnApplicationClanName = ""; public bool HasClan => !string.IsNullOrWhiteSpace(ClanId); public bool IsLeader { get { if (HasClan) { return SelfRole == ClanRole.Leader; } return false; } } public bool CanModerate { get { bool flag = HasClan; if (flag) { ClanRole selfRole = SelfRole; bool flag2 = (uint)selfRole <= 1u; flag = flag2; } return flag; } } public bool HasPrimaryClan => !string.IsNullOrWhiteSpace(PrimaryClanId); public bool HasGuestClan => !string.IsNullOrWhiteSpace(GuestClanId); public bool HasAnyClan { get { if (!HasPrimaryClan) { return HasGuestClan; } return true; } } public bool HasOwnApplication => !string.IsNullOrWhiteSpace(OwnApplicationClanId); public bool IsConnectedToClan(string clanId) { if (!string.IsNullOrWhiteSpace(clanId)) { if (!StringComparer.Ordinal.Equals(PrimaryClanId, clanId)) { return StringComparer.Ordinal.Equals(GuestClanId, clanId); } return true; } return false; } public ClanRole? GetRoleForClan(string clanId) { if (!string.IsNullOrWhiteSpace(clanId) && StringComparer.Ordinal.Equals(PrimaryClanId, clanId)) { return PrimaryRole; } if (!string.IsNullOrWhiteSpace(clanId) && StringComparer.Ordinal.Equals(GuestClanId, clanId)) { return ClanRole.Guest; } return null; } public void Write(ZPackage package) { ClanDataRules.RequireCount(Roster.Count, 512, "roster"); ClanDataRules.RequireCount(Applications.Count, 512, "application"); ClanDataRules.WriteText(package, Status, 1024, "snapshot status", allowEmpty: true, allowLineBreaks: true); package.Write(ClanDataRules.RequireOptionalRequestId(ResponseRequestId)); package.Write((int)ClanDataRules.RequireEnum(ResponseResultCode, "operation result")); ClanDataRules.WriteOptionalClanId(package, ClanId); ClanDataRules.WriteOptionalClanName(package, ClanName); ClanDataRules.WriteClanDescription(package, ClanDescription); ClanDataRules.WriteClanEmblemKey(package, ClanEmblemKey); package.Write((int)ClanDataRules.RequireEnum(SelfRole, "self role")); ClanDataRules.WriteOptionalClanId(package, PrimaryClanId, "primary clan id"); ClanDataRules.WriteOptionalClanName(package, PrimaryClanName, "primary clan name"); package.Write((int)ClanDataRules.RequireEnum(PrimaryRole, "primary clan role")); ClanDataRules.WriteOptionalClanId(package, GuestClanId, "guest clan id"); ClanDataRules.WriteOptionalClanName(package, GuestClanName, "guest clan name"); package.Write(Roster.Count); foreach (ClanPlayerSummary item in Roster) { item.Write(package); } package.Write(Applications.Count); foreach (ClanApplicationSummary application in Applications) { application.Write(package); } package.Write(Invite != null); Invite?.Write(package); package.Write(HasOwnApplication); if (HasOwnApplication) { ClanDataRules.WriteClanId(package, OwnApplicationClanId, "own application clan id"); ClanDataRules.WriteClanName(package, OwnApplicationClanName, "own application clan name"); } } public static ClanClientSnapshot Read(ZPackage package) { ClanClientSnapshot clanClientSnapshot = new ClanClientSnapshot { Status = ClanDataRules.ReadText(package, 1024, "snapshot status", allowEmpty: true, allowLineBreaks: true), ResponseRequestId = ClanDataRules.RequireOptionalRequestId(package.ReadLong()), ResponseResultCode = ClanDataRules.ReadEnum(package, "operation result"), ClanId = ClanDataRules.ReadOptionalClanId(package), ClanName = ClanDataRules.ReadOptionalClanName(package), ClanDescription = ClanDataRules.ReadClanDescription(package), ClanEmblemKey = ClanDataRules.ReadClanEmblemKey(package), SelfRole = ClanDataRules.ReadEnum(package, "self role"), PrimaryClanId = ClanDataRules.ReadOptionalClanId(package, "primary clan id"), PrimaryClanName = ClanDataRules.ReadOptionalClanName(package, "primary clan name"), PrimaryRole = ClanDataRules.ReadEnum(package, "primary clan role"), GuestClanId = ClanDataRules.ReadOptionalClanId(package, "guest clan id"), GuestClanName = ClanDataRules.ReadOptionalClanName(package, "guest clan name") }; ReadList(package, clanClientSnapshot.Roster, 512, "roster", ClanPlayerSummary.Read); ReadList(package, clanClientSnapshot.Applications, 512, "application", ClanApplicationSummary.Read); if (package.ReadBool()) { clanClientSnapshot.Invite = ClanInviteSummary.Read(package); } if (package.ReadBool()) { clanClientSnapshot.OwnApplicationClanId = ClanDataRules.ReadClanId(package, "own application clan id"); clanClientSnapshot.OwnApplicationClanName = ClanDataRules.ReadClanName(package, "own application clan name"); } return clanClientSnapshot; } public bool ContainsClanPlayer(ClanPlayerRef player) { if (!player.IsValid) { return false; } foreach (ClanPlayerSummary item in Roster) { if (item.IsOnline && StringComparer.Ordinal.Equals(item.Id, player.Id)) { return true; } } return false; } private static void ReadList(ZPackage package, List items, int maximum, string fieldName, Func read) { int num = ClanDataRules.ReadCount(package, maximum, fieldName); for (int i = 0; i < num; i++) { items.Add(read(package)); } } } internal sealed class ClanDirectoryPlayerSummary { public string PlayerId = ""; public string PlayerName = ""; public ClanDirectoryPlayerState State; public string ClanName = ""; public bool IsOnline; public bool IsSelf; public bool CanInvite; public bool CanResolveApplication; public long LastSeenUtcTicks; public void Write(ZPackage package) { ClanDataRules.WritePlayerKey(package, PlayerId, "directory player key"); ClanDataRules.WritePlayerName(package, PlayerName, "directory player name"); package.Write((int)ClanDataRules.RequireEnum(State, "directory player state")); ClanDataRules.WriteOptionalClanName(package, ClanName, "directory clan name"); package.Write(IsOnline); package.Write(IsSelf); package.Write(CanInvite); package.Write(CanResolveApplication); if (LastSeenUtcTicks >= 0) { long lastSeenUtcTicks = LastSeenUtcTicks; DateTime maxValue = DateTime.MaxValue; if (lastSeenUtcTicks <= maxValue.Ticks) { package.Write(LastSeenUtcTicks); return; } } throw new InvalidDataException("directory last-seen timestamp is invalid."); } public static ClanDirectoryPlayerSummary Read(ZPackage package) { ClanDirectoryPlayerSummary clanDirectoryPlayerSummary = new ClanDirectoryPlayerSummary { PlayerId = ClanDataRules.ReadPlayerKey(package, "directory player key"), PlayerName = ClanDataRules.ReadPlayerName(package, "directory player name"), State = ClanDataRules.ReadEnum(package, "directory player state"), ClanName = ClanDataRules.ReadOptionalClanName(package, "directory clan name"), IsOnline = package.ReadBool(), IsSelf = package.ReadBool(), CanInvite = package.ReadBool(), CanResolveApplication = package.ReadBool() }; clanDirectoryPlayerSummary.LastSeenUtcTicks = package.ReadLong(); if (clanDirectoryPlayerSummary.LastSeenUtcTicks >= 0) { long lastSeenUtcTicks = clanDirectoryPlayerSummary.LastSeenUtcTicks; DateTime maxValue = DateTime.MaxValue; if (lastSeenUtcTicks <= maxValue.Ticks) { return clanDirectoryPlayerSummary; } } throw new InvalidDataException("directory last-seen timestamp is invalid."); } } internal sealed class ClanDirectorySnapshot { public long RequestId; public ClanOperationResultCode ResultCode; public string Status = ""; public bool IsTruncated; public readonly List PublicClans = new List(); public readonly List Players = new List(); internal void Write(ZPackage package, int publicClanCount, int playerCount) { publicClanCount = ClanDataRules.RequireRange(publicClanCount, 0, Math.Min(PublicClans.Count, 1024), "directory clan count"); playerCount = ClanDataRules.RequireRange(playerCount, 0, Math.Min(Players.Count, 1024), "directory player count"); package.Write(ClanDataRules.RequireRequestId(RequestId)); package.Write((int)ClanDataRules.RequireEnum(ResultCode, "directory result")); ClanDataRules.WriteText(package, Status, 1024, "directory status", allowEmpty: true, allowLineBreaks: true); package.Write(IsTruncated); package.Write(publicClanCount); for (int i = 0; i < publicClanCount; i++) { PublicClans[i].Write(package); } package.Write(playerCount); for (int j = 0; j < playerCount; j++) { Players[j].Write(package); } } public static ClanDirectorySnapshot Read(ZPackage package) { ClanDirectorySnapshot clanDirectorySnapshot = new ClanDirectorySnapshot { RequestId = ClanDataRules.RequireRequestId(package.ReadLong()), ResultCode = ClanDataRules.ReadEnum(package, "directory result"), Status = ClanDataRules.ReadText(package, 1024, "directory status", allowEmpty: true, allowLineBreaks: true), IsTruncated = package.ReadBool() }; int num = ClanDataRules.ReadCount(package, 1024, "directory clan"); for (int i = 0; i < num; i++) { clanDirectorySnapshot.PublicClans.Add(ClanPublicSummary.Read(package)); } int num2 = ClanDataRules.ReadCount(package, 1024, "directory player"); for (int j = 0; j < num2; j++) { clanDirectorySnapshot.Players.Add(ClanDirectoryPlayerSummary.Read(package)); } return clanDirectorySnapshot; } } internal static class ClanLocalizationManager { private static readonly string[] SupportedExtensions = new string[2] { ".yml", ".json" }; private const long MaximumExternalTranslationBytes = 1048576L; private const int MaximumTranslationValueLength = 4096; private static readonly object Sync = new object(); private static readonly HashSet LoggedWarnings = new HashSet(StringComparer.Ordinal); private static BaseUnityPlugin? _plugin; private static ManualLogSource? _logger; private static Dictionary _embeddedEnglish = new Dictionary(StringComparer.Ordinal); private static Dictionary _currentTexts = new Dictionary(StringComparer.Ordinal); private static string _currentLanguage = "English"; private static bool _hasLoadedLanguage; private static bool _initialized; internal static event Action? LanguageChanged; internal static void Initialize(BaseUnityPlugin plugin, Harmony harmony) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown if (!_initialized) { _plugin = plugin; _logger = ClanPlugin.ClanLogger; _embeddedEnglish = LoadEmbeddedTranslation("English") ?? throw new InvalidOperationException(plugin.Info.Metadata.Name + " has no embedded translations/English.yml or translations/English.json resource."); _currentTexts = new Dictionary(_embeddedEnglish, StringComparer.Ordinal); _initialized = true; harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ClanLocalizationManager), "AfterSetupLanguage", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "SetupGui", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ClanLocalizationManager), "AfterSetupGui", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Localization instance = Localization.instance; if (instance != null) { Reload(SafeSelectedLanguage(instance)); } } } internal static void Dispose() { lock (Sync) { _plugin = null; _logger = null; _embeddedEnglish = new Dictionary(StringComparer.Ordinal); _currentTexts = new Dictionary(StringComparer.Ordinal); _currentLanguage = "English"; _hasLoadedLanguage = false; LoggedWarnings.Clear(); ClanLocalizationManager.LanguageChanged = null; _initialized = false; } } internal static string GetText(string key) { string text = NormalizeKey(key); lock (Sync) { if (_currentTexts.TryGetValue(text, out string value)) { return value; } if (_embeddedEnglish.TryGetValue(text, out string value2)) { return value2; } } WarnOnce("missing:" + text, "Missing Clan localization key '" + text + "'."); return text; } private static void AfterSetupLanguage(string language) { Reload(language); } private static void AfterSetupGui() { Localization instance = Localization.instance; if (instance != null && NeedsReload(SafeSelectedLanguage(instance))) { Reload(SafeSelectedLanguage(instance)); } } private static bool NeedsReload(string language) { lock (Sync) { return !_hasLoadedLanguage || !_currentLanguage.Equals(language, StringComparison.OrdinalIgnoreCase); } } private static void Reload(string language) { if (!_initialized || (Object)(object)_plugin == (Object)null) { return; } string text = (string.IsNullOrWhiteSpace(language) ? "English" : language.Trim()); Dictionary> externalFiles = FindExternalTranslations(); Dictionary dictionary = new Dictionary(_embeddedEnglish, StringComparer.Ordinal); MergeExternalTranslation(dictionary, externalFiles, "English"); if (!text.Equals("English", StringComparison.OrdinalIgnoreCase)) { Dictionary dictionary2 = LoadEmbeddedTranslation(text); if (dictionary2 != null) { Merge(dictionary, ValidateTranslation("embedded " + text + " translation", dictionary2)); } MergeExternalTranslation(dictionary, externalFiles, text); } lock (Sync) { _currentTexts = dictionary; _currentLanguage = text; _hasLoadedLanguage = true; } InvokeLanguageChanged(); } private static Dictionary> FindExternalTranslations() { BaseUnityPlugin plugin = _plugin; if ((Object)(object)plugin == (Object)null) { return new Dictionary>(StringComparer.OrdinalIgnoreCase); } string name = plugin.Info.Metadata.Name; string text = Directory.GetParent(Paths.PluginPath)?.FullName; if (string.IsNullOrWhiteSpace(text) || !Directory.Exists(text)) { WarnOnce("missing-bepinex-root", "Could not locate the BepInEx root while searching for Clan translations."); return new Dictionary>(StringComparer.OrdinalIgnoreCase); } Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (string item in EnumerateFilesSafely(text)) { string extension = Path.GetExtension(item); if (!SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { continue; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); string text2 = name + "."; if (fileNameWithoutExtension.StartsWith(text2, StringComparison.OrdinalIgnoreCase) && fileNameWithoutExtension.Length != text2.Length) { string key = fileNameWithoutExtension.Substring(text2.Length); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List()); } value.Add(item); } } foreach (List value2 in dictionary.Values) { value2.Sort(CompareExternalCandidates); } return dictionary; } private static IEnumerable EnumerateFilesSafely(string root) { Stack pending = new Stack(); pending.Push(Path.GetFullPath(root)); while (pending.Count > 0) { string directory = pending.Pop(); string[] files; try { files = Directory.GetFiles(directory); } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException) ? 1 : 0) != 0) { WarnOnce("scan-files:" + directory, "Could not scan '" + directory + "' for Clan translations: " + ex.Message); continue; } Array.Sort(files, (IComparer?)StringComparer.OrdinalIgnoreCase); string[] array = files; for (int i = 0; i < array.Length; i++) { yield return array[i]; } string[] directories; try { directories = Directory.GetDirectories(directory); } catch (Exception ex2) when (((ex2 is IOException || ex2 is UnauthorizedAccessException) ? 1 : 0) != 0) { WarnOnce("scan-directories:" + directory, "Could not scan subdirectories of '" + directory + "' for Clan translations: " + ex2.Message); continue; } Array.Sort(directories, (IComparer?)StringComparer.OrdinalIgnoreCase); for (int num = directories.Length - 1; num >= 0; num--) { string text = directories[num]; try { if ((File.GetAttributes(text) & FileAttributes.ReparsePoint) == 0) { pending.Push(text); } } catch (Exception ex3) when (((ex3 is IOException || ex3 is UnauthorizedAccessException) ? 1 : 0) != 0) { WarnOnce("scan-attributes:" + text, "Could not inspect '" + text + "' while searching for Clan translations: " + ex3.Message); } } } } private static int CompareExternalCandidates(string left, string right) { int num = IsUnderDirectory(left, Paths.ConfigPath).CompareTo(IsUnderDirectory(right, Paths.ConfigPath)); if (num != 0) { return -num; } int num2 = ExtensionPriority(left).CompareTo(ExtensionPriority(right)); if (num2 == 0) { return StringComparer.OrdinalIgnoreCase.Compare(Path.GetFullPath(left), Path.GetFullPath(right)); } return num2; } private static int ExtensionPriority(string path) { return (!Path.GetExtension(path).Equals(".yml", StringComparison.OrdinalIgnoreCase)) ? 1 : 0; } private static bool IsUnderDirectory(string path, string directory) { string fullPath = Path.GetFullPath(path); string text = Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); char directorySeparatorChar = Path.DirectorySeparatorChar; string value = text + directorySeparatorChar; return fullPath.StartsWith(value, StringComparison.OrdinalIgnoreCase); } private static void MergeExternalTranslation(IDictionary target, IReadOnlyDictionary> externalFiles, string language) { if (!externalFiles.TryGetValue(language, out List value)) { return; } for (int i = 0; i < value.Count; i++) { string text = value[i]; Dictionary dictionary = TryLoadExternalTranslation(text); if (dictionary != null) { Merge(target, dictionary); for (int j = i + 1; j < value.Count; j++) { WarnOnce("duplicate:" + language + ":" + value[j], "Ignoring duplicate Clan " + language + " translation '" + value[j] + "'; using '" + text + "'."); } break; } } } private static Dictionary? TryLoadExternalTranslation(string path) { try { long length = new FileInfo(path).Length; if (length <= 0 || length > 1048576) { throw new InvalidDataException($"translation size must be between 1 and {1048576L} bytes"); } Dictionary dictionary = ParseTranslation(File.ReadAllText(path, Encoding.UTF8)); if (dictionary == null || dictionary.Count == 0) { throw new InvalidDataException("the translation file is empty"); } dictionary = ValidateTranslation(path, dictionary); if (dictionary.Count == 0) { throw new InvalidDataException("the translation file has no valid Clan keys"); } ManualLogSource? logger = _logger; if (logger != null) { logger.LogInfo((object)("Loaded external Clan translation '" + path + "'.")); } return dictionary; } catch (Exception ex) { WarnOnce("external:" + path + ":" + ex.GetType().FullName + ":" + ex.Message, "Could not load external Clan translation '" + path + "': " + ex.Message + ". Falling back to the next valid source."); return null; } } private static Dictionary ValidateTranslation(string source, IReadOnlyDictionary translation) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair item in translation) { if (!_embeddedEnglish.TryGetValue(item.Key, out string value)) { WarnOnce("unknown-key:" + source + ":" + item.Key, "Ignored unknown Clan translation key '" + item.Key + "' in '" + source + "'."); } else if (item.Value.Length > 4096) { WarnOnce("long-value:" + source + ":" + item.Key, $"Ignored Clan translation key '{item.Key}' in '{source}' because its value exceeds {4096} characters."); } else if (!HasCompatiblePlaceholders(value, item.Value)) { WarnOnce("placeholders:" + source + ":" + item.Key, "Ignored Clan translation key '" + item.Key + "' in '" + source + "' because its format placeholders do not match English."); } else { dictionary[item.Key] = item.Value; } } return dictionary; } private static bool HasCompatiblePlaceholders(string english, string candidate) { if (TryReadPlaceholderIndexes(english, out HashSet indexes) && TryReadPlaceholderIndexes(candidate, out HashSet indexes2)) { return indexes.SetEquals(indexes2); } return false; } private static bool TryReadPlaceholderIndexes(string template, out HashSet indexes) { indexes = new HashSet(); int num = 0; while (num < template.Length) { switch (template[num]) { case '{': { if (num + 1 < template.Length && template[num + 1] == '{') { num += 2; break; } int num2 = template.IndexOf('}', num + 1); if (num2 < 0 || !int.TryParse(template.Substring(num + 1, num2 - num - 1), NumberStyles.None, CultureInfo.InvariantCulture, out var result)) { return false; } indexes.Add(result); num = num2 + 1; break; } case '}': if (num + 1 >= template.Length || template[num + 1] != '}') { return false; } num += 2; break; default: num++; break; } } return true; } private static Dictionary? LoadEmbeddedTranslation(string language) { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string[] supportedExtensions = SupportedExtensions; foreach (string text in supportedExtensions) { string suffix = "translations." + language + text; string text2 = executingAssembly.GetManifestResourceNames().OrderBy((string name) => name, StringComparer.Ordinal).FirstOrDefault((string name) => name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); if (text2 == null) { continue; } using Stream stream = executingAssembly.GetManifestResourceStream(text2); if (stream == null) { continue; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); return ParseTranslation(streamReader.ReadToEnd()); } return null; } private static Dictionary? ParseTranslation(string data) { Dictionary dictionary = new DeserializerBuilder().IgnoreFields().WithDuplicateKeyChecking().Build() .Deserialize>(data); if (dictionary == null) { return null; } Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair item in dictionary) { string text = NormalizeKey(item.Key); if (!string.IsNullOrWhiteSpace(text) && item.Value != null) { dictionary2[text] = item.Value; } } return dictionary2; } private static void Merge(IDictionary target, IReadOnlyDictionary? source) { if (source == null) { return; } foreach (KeyValuePair item in source) { target[item.Key] = item.Value; } } private static string SafeSelectedLanguage(Localization localization) { try { return localization.GetSelectedLanguage(); } catch (Exception ex) { WarnOnce("selected-language", "Could not read the selected language: " + ex.Message + ". Using English."); return _currentLanguage; } } private static string NormalizeKey(string key) { return (key ?? string.Empty).Trim().TrimStart(new char[1] { '$' }); } private static void InvokeLanguageChanged() { Delegate[] array = ClanLocalizationManager.LanguageChanged?.GetInvocationList() ?? Array.Empty(); foreach (Delegate obj in array) { try { ((Action)obj)(); } catch (Exception ex) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogWarning((object)("Clan language-change subscriber failed: " + ex.GetBaseException().Message)); } } } } private static void WarnOnce(string identity, string message) { lock (Sync) { if (!LoggedWarnings.Add(identity)) { return; } } ManualLogSource? logger = _logger; if (logger != null) { logger.LogWarning((object)message); } } } internal static class ClanLocalization { private const string WireStatusPrefix = "@ClanL10n1:"; private const int MaximumWireStatusLength = 1024; private const int MaximumWireArgumentCount = 8; private const int MaximumWireArgumentUtf8Bytes = 256; private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static bool _malformedWireStatusLogged; internal static event Action? LanguageChanged { add { ClanLocalizationManager.LanguageChanged += value; } remove { ClanLocalizationManager.LanguageChanged -= value; } } internal static void Initialize(BaseUnityPlugin plugin, Harmony harmony) { ClanLocalizationManager.Initialize(plugin, harmony); } internal static void Dispose() { ClanLocalizationManager.Dispose(); } internal static string Text(string key) { return ClanLocalizationManager.GetText(key); } internal static string Format(string key, params object[] arguments) { string text = Text(key); try { return string.Format(CultureInfo.CurrentCulture, text, arguments); } catch (FormatException ex) { ClanPlugin.ClanLogger.LogWarning((object)("Invalid format string for Clan localization key '" + key + "': " + ex.Message)); return text; } } internal static string EncodeStatus(string key, params object[] arguments) { string text = (key ?? string.Empty).Trim().TrimStart(new char[1] { '$' }); if (text.Length == 0) { return string.Empty; } if (text.Length > 96 || !IsSafeWireKey(text)) { ClanPlugin.ClanLogger.LogWarning((object)("Refused to encode invalid Clan status localization key '" + text + "'.")); return text; } object[] array = arguments ?? Array.Empty(); int num = Math.Min(array.Length, 8); StringBuilder stringBuilder = new StringBuilder("@ClanL10n1:".Length + text.Length + num * 24); stringBuilder.Append("@ClanL10n1:").Append(text); for (int i = 0; i < num; i++) { string s = LimitUtf8(Convert.ToString(array[i], CultureInfo.InvariantCulture) ?? string.Empty, 256); stringBuilder.Append('|').Append(Convert.ToBase64String(Encoding.UTF8.GetBytes(s))); } if (stringBuilder.Length <= 1024) { return stringBuilder.ToString(); } ClanPlugin.ClanLogger.LogWarning((object)("Clan status localization payload for '" + text + "' exceeded its wire limit.")); return "@ClanL10n1:" + text; } internal static string ResolveStatus(string value) { if (string.IsNullOrEmpty(value) || !value.StartsWith("@ClanL10n1:", StringComparison.Ordinal)) { return value ?? string.Empty; } try { if (value.Length > 1024) { throw new FormatException("status payload exceeds its wire limit"); } string[] array = value.Substring("@ClanL10n1:".Length).Split(new char[1] { '|' }); if (array.Length == 0 || array.Length > 9 || array[0].Length == 0 || array[0].Length > 96 || !IsSafeWireKey(array[0])) { throw new FormatException("status payload contains an invalid localization key or argument count"); } if (array.Length == 1) { return Text(array[0]); } List list = new List(array.Length - 1); for (int i = 1; i < array.Length; i++) { byte[] array2 = Convert.FromBase64String(array[i]); if (array2.Length > 256) { throw new FormatException("status argument exceeds its wire limit"); } list.Add(StrictUtf8.GetString(array2)); } return Format(array[0], list.ToArray()); } catch (Exception ex) when (((ex is FormatException || ex is DecoderFallbackException) ? 1 : 0) != 0) { if (!_malformedWireStatusLogged) { _malformedWireStatusLogged = true; ClanPlugin.ClanLogger.LogWarning((object)("Ignored a malformed localized Clan status payload: " + ex.Message)); } return Text("clan_status_unavailable"); } } internal static string Role(ClanRole role) { return role switch { ClanRole.Leader => Text("clan_role_leader"), ClanRole.Officer => Text("clan_role_officer"), ClanRole.Member => Text("clan_role_member"), ClanRole.Guest => Text("clan_role_guest"), _ => role.ToString(), }; } private static bool IsSafeWireKey(string key) { foreach (char c in key) { if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' && c != '-' && c != '.') { return false; } } return true; } private static string LimitUtf8(string value, int maximumBytes) { if (Encoding.UTF8.GetByteCount(value) <= maximumBytes) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); int num = 0; int num2; for (num2 = 0; num2 < value.Length; num2++) { int num3 = ((!char.IsHighSurrogate(value[num2]) || num2 + 1 >= value.Length || !char.IsLowSurrogate(value[num2 + 1])) ? 1 : 2); string text = value.Substring(num2, num3); int byteCount = Encoding.UTF8.GetByteCount(text); if (num + byteCount > maximumBytes) { break; } stringBuilder.Append(text); num += byteCount; num2 += num3 - 1; } return stringBuilder.ToString(); } } public enum ClanWardAuthorizationResolution { Unavailable, ResolvedNoAuthorization, Authorized } public enum ClanMembershipResolution { Unavailable, Resolved } public sealed class ClanInfo { public string ClanId { get; } public string Name { get; } public string Description { get; } public string EmblemKey { get; } public string LeaderName { get; } public int MemberCount { get; } internal ClanInfo(string clanId, string name, string description, string emblemKey, string leaderName, int memberCount) { ClanId = clanId; Name = name; Description = description; EmblemKey = emblemKey; LeaderName = leaderName; MemberCount = memberCount; } } public sealed class ClanMemberInfo { public string PlayerKey { get; } public string PlatformId { get; } public long PlayerId { get; } public string Name { get; } public ClanRole Role { get; } public bool IsSelf { get; } public bool IsOnline { get; } internal ClanMemberInfo(ClanPlayerSummary source) { PlayerKey = source.Player.Id; PlatformId = source.Player.PlatformId; PlayerId = source.Player.CharacterPlayerId; Name = source.Player.Name; Role = source.Role; IsSelf = source.IsSelf; IsOnline = source.IsOnline; } } public sealed class ClanLocalSnapshot { private static readonly IReadOnlyList NoMembers = new ReadOnlyCollection(Array.Empty()); public bool IsReady { get; } public bool HasClan => Clan != null; public bool HasPrimaryClan => !string.IsNullOrWhiteSpace(PrimaryClanId); public bool HasGuestClan => !string.IsNullOrWhiteSpace(GuestClanId); public bool HasAnyClan { get { if (!HasPrimaryClan) { return HasGuestClan; } return true; } } public string EffectiveClanId => Clan?.ClanId ?? ""; public string PrimaryClanId { get; } public string PrimaryClanName { get; } public ClanRole? PrimaryRole { get; } public string GuestClanId { get; } public string GuestClanName { get; } public ClanInfo? Clan { get; } public ClanRole? SelfRole { get; } public IReadOnlyList Members { get; } internal static ClanLocalSnapshot Empty { get; } = new ClanLocalSnapshot(isReady: false, "", "", null, "", "", null, null, NoMembers); internal ClanLocalSnapshot(bool isReady, string primaryClanId, string primaryClanName, ClanRole? primaryRole, string guestClanId, string guestClanName, ClanInfo? clan, ClanRole? selfRole, IReadOnlyList members) { IsReady = isReady; PrimaryClanId = primaryClanId ?? ""; PrimaryClanName = primaryClanName ?? ""; PrimaryRole = primaryRole; GuestClanId = guestClanId ?? ""; GuestClanName = guestClanName ?? ""; Clan = clan; SelfRole = selfRole; Members = members; } } public enum ClanRenameResultCode { Unknown, Success, Unchanged, InvalidName, NotConnected, IdentityUnavailable, NotLeader, ClanChanged, NameAlreadyExists, RateLimited, TimedOut, SessionEnded, Failed } public sealed class ClanRenameResult { public long RequestId { get; } public ClanRenameResultCode Code { get; } public bool Succeeded { get { ClanRenameResultCode code = Code; if ((uint)(code - 1) <= 1u) { return true; } return false; } } public string RequestedName { get; } public string Message { get; } public ClanLocalSnapshot State { get; } internal ClanRenameResult(long requestId, ClanRenameResultCode code, string requestedName, string message, ClanLocalSnapshot state) { RequestId = requestId; Code = code; RequestedName = requestedName; Message = message; State = state; } } public sealed class ClanRenameRequestHandle { private readonly object _gate = new object(); private Action? _completed; private ClanRenameResult? _result; public long RequestId { get; } public bool IsCompleted { get { lock (_gate) { return _result != null; } } } public ClanRenameResult? Result { get { lock (_gate) { return _result; } } } public event Action? Completed { add { if (value == null) { return; } ClanRenameResult result; lock (_gate) { result = _result; if (result == null) { _completed = (Action)Delegate.Combine(_completed, value); return; } } InvokeSubscriber(value, result); } remove { lock (_gate) { _completed = (Action)Delegate.Remove(_completed, value); } } } internal ClanRenameRequestHandle(long requestId) { RequestId = requestId; } public bool TryGetResult(out ClanRenameResult? result) { lock (_gate) { result = _result; return result != null; } } internal bool Complete(ClanRenameResult result) { Action completed; lock (_gate) { if (_result != null) { return false; } _result = result; completed = _completed; _completed = null; } if (completed != null) { Delegate[] invocationList = completed.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { InvokeSubscriber((Action)invocationList[i], result); } } return true; } private static void InvokeSubscriber(Action subscriber, ClanRenameResult result) { try { subscriber(result); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan API rename completion subscriber failed: {arg}"); } } } public static class ClanApi { private sealed class PendingRename { public ClanRenameRequestHandle Handle { get; } public string RequestedName { get; } public float Deadline { get; } public PendingRename(ClanRenameRequestHandle handle, string requestedName, float deadline) { Handle = handle; RequestedName = requestedName; Deadline = deadline; } } public const int ApiVersion = 4; private const float RenameTimeoutSeconds = 15f; private static readonly object PendingLock = new object(); private static readonly Dictionary PendingRenames = new Dictionary(); private static readonly IReadOnlyList NoClans = new ReadOnlyCollection(Array.Empty()); private static bool _initialized; private static long _registryRevision; public static ClanLocalSnapshot Current { get; private set; } = ClanLocalSnapshot.Empty; public static IReadOnlyList KnownClans { get; private set; } = NoClans; public static long RegistryRevision => Interlocked.Read(in _registryRevision); public static event Action? StateChanged; public static event Action>? DirectoryChanged; public static event Action? RenameCompleted; public static event Action? WardAuthorizationChanged; public static event Action? RegistryChanged; public static ClanWardAuthorizationResolution ResolveWardAuthorization(string platformId, long characterPlayerId, out string clanId, out string clanName) { return ClanRegistry.ResolveWardAuthorization(platformId, characterPlayerId, out clanId, out clanName); } public static ClanMembershipResolution ResolveMemberships(string platformId, long characterPlayerId, out string primaryClanId, out string primaryClanName, out string guestClanId, out string guestClanName) { return ClanRegistry.ResolveMemberships(platformId, characterPlayerId, out primaryClanId, out primaryClanName, out guestClanId, out guestClanName); } internal static void Initialize() { if (!_initialized) { _initialized = true; ClanRpc.SnapshotChanged += OnSnapshotChanged; ClanRpc.DirectoryChanged += OnDirectoryChanged; Current = CreateLocalSnapshot(ClanRpc.CurrentSnapshot); KnownClans = CreateDirectory(ClanRpc.CurrentDirectory); } } internal static void Dispose() { if (_initialized) { ClanRpc.SnapshotChanged -= OnSnapshotChanged; ClanRpc.DirectoryChanged -= OnDirectoryChanged; CompleteAll(ClanRenameResultCode.SessionEnded, "Clan API was shut down."); _initialized = false; Current = ClanLocalSnapshot.Empty; KnownClans = NoClans; ClanApi.StateChanged = null; ClanApi.DirectoryChanged = null; ClanApi.RenameCompleted = null; ClanApi.WardAuthorizationChanged = null; ClanApi.RegistryChanged = null; } } internal static void NotifyRegistryChanged() { Interlocked.Increment(ref _registryRevision); InvokeInvalidationSubscribers(ClanApi.WardAuthorizationChanged, "ward authorization"); InvokeInvalidationSubscribers(ClanApi.RegistryChanged, "registry"); } private static void InvokeInvalidationSubscribers(Action? subscribers, string eventName) { if (subscribers == null) { return; } Delegate[] invocationList = subscribers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan API {eventName} subscriber failed: {arg}"); } } } internal static void ResetSession() { ClanLocalSnapshot clanLocalSnapshot = CreateLocalSnapshot(ClanRpc.CurrentSnapshot); bool num = !HasSameState(Current, clanLocalSnapshot); Current = clanLocalSnapshot; if (num) { InvokeSubscribers(ClanApi.StateChanged, Current, "state"); } bool num2 = KnownClans.Count != 0; KnownClans = NoClans; if (num2) { InvokeSubscribers>(ClanApi.DirectoryChanged, KnownClans, "directory"); } CompleteAll(ClanRenameResultCode.SessionEnded, "The clan session ended."); } internal static void Tick() { List list = null; float realtimeSinceStartup = Time.realtimeSinceStartup; lock (PendingLock) { foreach (PendingRename value in PendingRenames.Values) { if (realtimeSinceStartup >= value.Deadline) { if (list == null) { list = new List(); } list.Add(value); } } if (list != null) { foreach (PendingRename item in list) { PendingRenames.Remove(item.Handle.RequestId); } } } if (list == null) { return; } foreach (PendingRename item2 in list) { Complete(item2, ClanRenameResultCode.TimedOut, "The clan rename request timed out."); } } public static ClanInfo? GetClan(string clanId) { if (Current.Clan != null && StringComparer.Ordinal.Equals(Current.Clan.ClanId, clanId)) { return Current.Clan; } return KnownClans.FirstOrDefault((ClanInfo clan) => StringComparer.Ordinal.Equals(clan.ClanId, clanId)); } public static IReadOnlyList? GetMembers(string clanId) { if (Current.Clan == null || !StringComparer.Ordinal.Equals(Current.Clan.ClanId, clanId)) { return null; } return Current.Members; } public static long RequestDirectoryRefresh() { return ClanRpc.RequestDirectory(); } public static ClanRenameRequestHandle RequestRenameClan(string newName) { long num = ClanRpc.NextRequestId(); ClanRenameRequestHandle clanRenameRequestHandle = new ClanRenameRequestHandle(num); string text; try { text = ClanDataRules.RequireClanName(newName); } catch (InvalidDataException ex) { CompleteLocal(clanRenameRequestHandle, ClanRenameResultCode.InvalidName, newName ?? "", ex.Message); return clanRenameRequestHandle; } if (!Current.IsReady) { CompleteLocal(clanRenameRequestHandle, ClanRenameResultCode.IdentityUnavailable, text, "Clan identity is not ready."); return clanRenameRequestHandle; } if (Current.Clan == null) { CompleteLocal(clanRenameRequestHandle, ClanRenameResultCode.ClanChanged, text, "The local character does not belong to a clan."); return clanRenameRequestHandle; } PendingRename pendingRename = new PendingRename(clanRenameRequestHandle, text, Time.realtimeSinceStartup + 15f); lock (PendingLock) { PendingRenames.Add(num, pendingRename); } if (!ClanRpc.Send(new ClanRequest { Type = ClanRequestType.RenameClan, RequestId = num, ClanId = Current.Clan.ClanId, ClanName = text })) { bool flag; lock (PendingLock) { flag = PendingRenames.Remove(num); } if (flag) { Complete(pendingRename, ClanRenameResultCode.NotConnected, "Clan server is not connected."); } } return clanRenameRequestHandle; } private static void OnSnapshotChanged(ClanClientSnapshot snapshot) { ClanLocalSnapshot clanLocalSnapshot = CreateLocalSnapshot(snapshot); bool num = !HasSameState(Current, clanLocalSnapshot); Current = clanLocalSnapshot; if (num) { InvokeSubscribers(ClanApi.StateChanged, Current, "state"); } if (snapshot.ResponseRequestId <= 0) { return; } PendingRename value; lock (PendingLock) { if (!PendingRenames.TryGetValue(snapshot.ResponseRequestId, out value)) { return; } PendingRenames.Remove(snapshot.ResponseRequestId); } Complete(value, MapResult(snapshot), snapshot.Status); } private static void OnDirectoryChanged(ClanDirectorySnapshot directory) { IReadOnlyList readOnlyList = CreateDirectory(directory); bool num = !HasSameDirectory(KnownClans, readOnlyList); KnownClans = readOnlyList; if (num) { InvokeSubscribers>(ClanApi.DirectoryChanged, KnownClans, "directory"); } } private static ClanLocalSnapshot CreateLocalSnapshot(ClanClientSnapshot source) { IReadOnlyList members = new ReadOnlyCollection(source.Roster.Select((ClanPlayerSummary member) => new ClanMemberInfo(member)).ToList()); ClanInfo clan = null; if (source.HasClan) { string leaderName = source.Roster.FirstOrDefault((ClanPlayerSummary member) => member.Role == ClanRole.Leader)?.Name ?? ""; clan = new ClanInfo(source.ClanId, source.ClanName, source.ClanDescription, source.ClanEmblemKey, leaderName, source.Roster.Count); } return new ClanLocalSnapshot(ClanRpc.IsIdentityReady, source.PrimaryClanId, source.PrimaryClanName, source.HasPrimaryClan ? new ClanRole?(source.PrimaryRole) : ((ClanRole?)null), source.GuestClanId, source.GuestClanName, clan, source.HasClan ? new ClanRole?(source.SelfRole) : ((ClanRole?)null), members); } private static IReadOnlyList CreateDirectory(ClanDirectorySnapshot source) { return new ReadOnlyCollection(source.PublicClans.Select((ClanPublicSummary clan) => new ClanInfo(clan.ClanId, clan.Name, clan.Description, clan.EmblemKey, clan.LeaderName, clan.MemberCount)).ToList()); } private static ClanRenameResultCode MapResult(ClanClientSnapshot snapshot) { return snapshot.ResponseResultCode switch { ClanOperationResultCode.Success => ClanRenameResultCode.Success, ClanOperationResultCode.Unchanged => ClanRenameResultCode.Unchanged, ClanOperationResultCode.InvalidName => ClanRenameResultCode.InvalidName, ClanOperationResultCode.IdentityUnavailable => ClanRenameResultCode.IdentityUnavailable, ClanOperationResultCode.IdentityRejected => ClanRenameResultCode.IdentityUnavailable, ClanOperationResultCode.Unauthorized => ClanRenameResultCode.NotLeader, ClanOperationResultCode.ClanChanged => ClanRenameResultCode.ClanChanged, ClanOperationResultCode.NameTaken => ClanRenameResultCode.NameAlreadyExists, ClanOperationResultCode.RateLimited => ClanRenameResultCode.RateLimited, ClanOperationResultCode.Unavailable => ClanRenameResultCode.Failed, _ => ClanRenameResultCode.Failed, }; } private static void CompleteLocal(ClanRenameRequestHandle handle, ClanRenameResultCode code, string requestedName, string message) { ClanRenameResult clanRenameResult = new ClanRenameResult(handle.RequestId, code, requestedName, message, Current); if (handle.Complete(clanRenameResult)) { InvokeSubscribers(ClanApi.RenameCompleted, clanRenameResult, "rename completion"); } } private static void Complete(PendingRename pending, ClanRenameResultCode code, string message) { CompleteLocal(pending.Handle, code, pending.RequestedName, message); } private static void CompleteAll(ClanRenameResultCode code, string message) { PendingRename[] array; lock (PendingLock) { array = PendingRenames.Values.ToArray(); PendingRenames.Clear(); } PendingRename[] array2 = array; for (int i = 0; i < array2.Length; i++) { Complete(array2[i], code, message); } } private static bool HasSameState(ClanLocalSnapshot left, ClanLocalSnapshot right) { if (left.IsReady != right.IsReady || !StringComparer.Ordinal.Equals(left.PrimaryClanId, right.PrimaryClanId) || !StringComparer.Ordinal.Equals(left.PrimaryClanName, right.PrimaryClanName) || left.PrimaryRole != right.PrimaryRole || !StringComparer.Ordinal.Equals(left.GuestClanId, right.GuestClanId) || !StringComparer.Ordinal.Equals(left.GuestClanName, right.GuestClanName) || left.SelfRole != right.SelfRole || !HasSameClan(left.Clan, right.Clan) || left.Members.Count != right.Members.Count) { return false; } for (int i = 0; i < left.Members.Count; i++) { ClanMemberInfo clanMemberInfo = left.Members[i]; ClanMemberInfo clanMemberInfo2 = right.Members[i]; if (!StringComparer.Ordinal.Equals(clanMemberInfo.PlayerKey, clanMemberInfo2.PlayerKey) || !StringComparer.Ordinal.Equals(clanMemberInfo.Name, clanMemberInfo2.Name) || clanMemberInfo.Role != clanMemberInfo2.Role || clanMemberInfo.IsSelf != clanMemberInfo2.IsSelf || clanMemberInfo.IsOnline != clanMemberInfo2.IsOnline) { return false; } } return true; } private static bool HasSameDirectory(IReadOnlyList left, IReadOnlyList right) { if (left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { if (!HasSameClan(left[i], right[i])) { return false; } } return true; } private static bool HasSameClan(ClanInfo? left, ClanInfo? right) { if (left == right) { return true; } if (left != null && right != null && StringComparer.Ordinal.Equals(left.ClanId, right.ClanId) && StringComparer.Ordinal.Equals(left.Name, right.Name) && StringComparer.Ordinal.Equals(left.Description, right.Description) && StringComparer.Ordinal.Equals(left.EmblemKey, right.EmblemKey) && StringComparer.Ordinal.Equals(left.LeaderName, right.LeaderName)) { return left.MemberCount == right.MemberCount; } return false; } private static void InvokeSubscribers(Action? subscribers, T value, string eventName) { if (subscribers == null) { return; } Delegate[] invocationList = subscribers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(value); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan API {eventName} subscriber failed: {arg}"); } } } } internal static class ClanIdentity { private static readonly Platform SteamPlatform = new Platform("Steam"); public static ClanPlayerRef FromPeer(ZNetPeer? peer) { bool retryable; return FromPeer(peer, out retryable); } public static ClanPlayerRef FromPeer(ZNetPeer? peer, out bool retryable) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) retryable = true; if (peer == null) { ClanPlayerRef result = ClanPlayerRef.Local(); retryable = !result.IsValid; return result; } if (!peer.IsReady() || ((ZDOID)(ref peer.m_characterID)).IsNone() || (Object)(object)ZNet.instance == (Object)null) { return default(ClanPlayerRef); } if (((ZDOID)(ref peer.m_characterID)).UserID != peer.m_uid) { retryable = false; return default(ClanPlayerRef); } string text = ResolvePeerPlatformId(peer); if (string.IsNullOrWhiteSpace(text)) { return default(ClanPlayerRef); } long playerId = ReadCharacterPlayerId(peer.m_characterID); ClanPlayerRef result2 = new ClanPlayerRef(text, playerId, peer.m_playerName); retryable = !result2.IsValid; return result2; } public static ClanPlayerRef FromPlayer(Player player) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null)) { return FromCharacterId(((Character)player).GetZDOID()); } return default(ClanPlayerRef); } public unsafe static ClanPlayerRef FromPlayerInfo(PlayerInfo info) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref info.m_characterID)).IsNone()) { return default(ClanPlayerRef); } return new ClanPlayerRef(((object)(*(PlatformUserID*)(&info.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString(), ReadCharacterPlayerId(info.m_characterID), info.m_name); } public unsafe static bool TryMatchRosterPlayer(PlayerInfo info, IReadOnlyList roster, out ClanPlayerRef player) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) player = default(ClanPlayerRef); ClanPlayerRef exact = FromPlayerInfo(info); if (exact.IsValid && roster.Any((ClanPlayerSummary member) => member.Id == exact.Id)) { player = exact; return true; } string text = ClanDataRules.NormalizePlatformId(((object)(*(PlatformUserID*)(&info.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString()); if (text.Length == 0) { return false; } ClanPlayerRef clanPlayerRef = default(ClanPlayerRef); int num = 0; foreach (ClanPlayerSummary item in roster) { if (item.Player.IsValid && StringComparer.Ordinal.Equals(item.Player.PlatformId, text) && item.IsOnline) { num++; clanPlayerRef = item.Player; } } if (num == 1) { player = clanPlayerRef; return true; } return false; } public static ZNetPeer? FindPeer(ClanPlayerRef player) { if (!player.IsValid) { return null; } foreach (ZNetPeer connectedPeer in GetConnectedPeers()) { ClanPlayerRef player2; ClanPlayerRef clanPlayerRef = (ClanRpc.TryGetPinnedPeerIdentity(connectedPeer, out player2) ? player2 : FromPeer(connectedPeer)); if (clanPlayerRef.IsValid && StringComparer.Ordinal.Equals(clanPlayerRef.Id, player.Id)) { return connectedPeer; } } return null; } public static IEnumerable GetOnlinePlayerRefs() { HashSet yieldedPlayerIds = new HashSet(StringComparer.Ordinal); ClanPlayerRef clanPlayerRef = (((Object)(object)Player.m_localPlayer != (Object)null) ? ClanPlayerRef.Local() : default(ClanPlayerRef)); if (clanPlayerRef.IsValid && yieldedPlayerIds.Add(clanPlayerRef.Id)) { yield return clanPlayerRef; } foreach (ZNetPeer connectedPeer in GetConnectedPeers()) { ClanPlayerRef player; ClanPlayerRef clanPlayerRef2 = (ClanRpc.TryGetPinnedPeerIdentity(connectedPeer, out player) ? player : FromPeer(connectedPeer)); if (clanPlayerRef2.IsValid && yieldedPlayerIds.Add(clanPlayerRef2.Id)) { yield return clanPlayerRef2; } } } private static ClanPlayerRef FromCharacterId(ZDOID characterId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_0039: Unknown result type (might be due to invalid IL or missing references) if (characterId == ZDOID.None) { return default(ClanPlayerRef); } foreach (PlayerInfo onlinePlayer in GetOnlinePlayers()) { if (onlinePlayer.m_characterID == characterId) { return FromPlayerInfo(onlinePlayer); } } return default(ClanPlayerRef); } private static long ReadCharacterPlayerId(ZDOID characterId) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref characterId)).IsNone() || ZDOMan.instance == null) { return 0L; } ZDO zDO = ZDOMan.instance.GetZDO(characterId); if (zDO == null) { return 0L; } return zDO.GetLong(ZDOVars.s_playerID, 0L); } private unsafe static string ResolvePeerPlatformId(ZNetPeer peer) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerInfo onlinePlayer in GetOnlinePlayers()) { if (onlinePlayer.m_characterID == peer.m_characterID) { PlatformUserID id = onlinePlayer.m_userInfo.m_id; string text = ((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString(); if (!string.IsNullOrWhiteSpace(text)) { return text; } } } ISocket socket = peer.m_socket; string text2 = ((socket != null) ? socket.GetHostName() : null) ?? ""; if (string.IsNullOrWhiteSpace(text2) || (Object)(object)ZNet.instance == (Object)null) { return ""; } return ((object)(((int)ZNet.m_onlineBackend == 0) ? new PlatformUserID(SteamPlatform, text2) : new PlatformUserID(text2))/*cast due to .constrained prefix*/).ToString(); } private static IEnumerable GetConnectedPeers() { ZNet instance = ZNet.instance; IEnumerable enumerable = ((instance != null) ? instance.GetConnectedPeers() : null); return enumerable ?? Enumerable.Empty(); } internal static IEnumerable GetOnlinePlayers() { ZNet instance = ZNet.instance; IEnumerable enumerable = ((instance != null) ? instance.GetPlayerList() : null); return enumerable ?? Enumerable.Empty(); } } internal static class ClanRegistry { private readonly struct OperationOutcome { public readonly ClanOperationResultCode Code; public readonly string Status; public OperationOutcome(ClanOperationResultCode code, string status) { Code = code; Status = status; } } private sealed class HudCandidate { public ClanMember Member { get; } public ZDOID CharacterId { get; } public float DistanceSquared { get; } public HudCandidate(ClanMember member, ZDOID characterId, float distanceSquared) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Member = member; CharacterId = characterId; DistanceSquared = distanceSquared; } } private sealed class HudSelectionEntry { public string PlayerId => Player.Id; public ClanPlayerRef Player { get; } public ZDOID CharacterId { get; } public HudSelectionEntry(ClanPlayerRef player, ZDOID characterId) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Player = player; CharacterId = characterId; } } private readonly struct HudHealthState : IEquatable { public readonly bool HasHealth; public readonly float CurrentHealth; public readonly float MaxHealth; public HudHealthState(bool hasHealth, float currentHealth, float maxHealth) { HasHealth = hasHealth; CurrentHealth = currentHealth; MaxHealth = maxHealth; } public bool Equals(HudHealthState other) { if (HasHealth == other.HasHealth) { float currentHealth = CurrentHealth; if (currentHealth.Equals(other.CurrentHealth)) { currentHealth = MaxHealth; return currentHealth.Equals(other.MaxHealth); } } return false; } public override bool Equals(object? obj) { if (obj is HudHealthState other) { return Equals(other); } return false; } public override int GetHashCode() { bool hasHealth = HasHealth; int num = hasHealth.GetHashCode() * 397; float currentHealth = CurrentHealth; int num2 = (num ^ currentHealth.GetHashCode()) * 397; currentHealth = MaxHealth; return num2 ^ currentHealth.GetHashCode(); } } private sealed class PendingHudUpdate { public ClanHudSnapshot Snapshot { get; } public IReadOnlyDictionary AbsoluteHealth { get; } public PendingHudUpdate(ClanHudSnapshot snapshot, IReadOnlyDictionary absoluteHealth) { Snapshot = snapshot; AbsoluteHealth = absoluteHealth; } } private sealed class HudViewerCache { public string ClanId = ""; public long SelectionRevision; public long AcknowledgedStateRevision; public float LastSelectionRefreshTime = float.NegativeInfinity; public float LastFullResendTime = float.NegativeInfinity; public bool SelectionRequiresFull = true; public readonly List Selection = new List(); public readonly Dictionary AcknowledgedHealth = new Dictionary(StringComparer.Ordinal); public PendingHudUpdate? PendingUpdate; } private sealed class YamlRegistryDocument { [YamlMember(Alias = "format_version", ApplyNamingConventions = false, Order = 1)] public int FormatVersion { get; set; } [YamlMember(Alias = "clans", ApplyNamingConventions = false, Order = 2)] public List? Clans { get; set; } [YamlMember(Alias = "pending_invites", ApplyNamingConventions = false, Order = 3)] public List? PendingInvites { get; set; } } private sealed class YamlClanDto { [YamlMember(Alias = "clan_id", ApplyNamingConventions = false, Order = 1)] public string? ClanId { get; set; } [YamlMember(Alias = "creation_order", ApplyNamingConventions = false, Order = 2)] public long CreationOrder { get; set; } [YamlMember(Alias = "name", ApplyNamingConventions = false, Order = 3)] public string? Name { get; set; } [YamlMember(Alias = "description", ApplyNamingConventions = false, Order = 4)] public string? Description { get; set; } [YamlMember(Alias = "emblem_key", ApplyNamingConventions = false, Order = 5)] public string? EmblemKey { get; set; } [YamlMember(Alias = "members", ApplyNamingConventions = false, Order = 6)] public List? Members { get; set; } [YamlMember(Alias = "applications", ApplyNamingConventions = false, Order = 7)] public List? Applications { get; set; } } private sealed class YamlMemberDto { [YamlMember(Alias = "player", ApplyNamingConventions = false, Order = 1)] public YamlPlayerDto? Player { get; set; } [YamlMember(Alias = "role", ApplyNamingConventions = false, Order = 2)] public string? Role { get; set; } } private sealed class YamlApplicationDto { [YamlMember(Alias = "player", ApplyNamingConventions = false, Order = 1)] public YamlPlayerDto? Player { get; set; } } private sealed class YamlInviteDto { [YamlMember(Alias = "invite_id", ApplyNamingConventions = false, Order = 1)] public string? InviteId { get; set; } [YamlMember(Alias = "clan_id", ApplyNamingConventions = false, Order = 2)] public string? ClanId { get; set; } [YamlMember(Alias = "from_name", ApplyNamingConventions = false, Order = 3)] public string? FromName { get; set; } [YamlMember(Alias = "target", ApplyNamingConventions = false, Order = 4)] public YamlPlayerDto? Target { get; set; } } private sealed class YamlPlayerDto { [YamlMember(Alias = "platform_id", ApplyNamingConventions = false, Order = 1)] public string? PlatformId { get; set; } [YamlMember(Alias = "player_id", ApplyNamingConventions = false, Order = 2)] public long CharacterPlayerId { get; set; } [YamlMember(Alias = "name", ApplyNamingConventions = false, Order = 3)] public string? Name { get; set; } } private sealed class RegistryData { public readonly Dictionary ClansById = new Dictionary(StringComparer.Ordinal); public readonly Dictionary ClansByName = new Dictionary(StringComparer.OrdinalIgnoreCase); public readonly Dictionary PrimaryClanByPlayerId = new Dictionary(StringComparer.Ordinal); public readonly Dictionary GuestClanByPlayerId = new Dictionary(StringComparer.Ordinal); public readonly Dictionary PendingInvitesByTarget = new Dictionary(StringComparer.Ordinal); } private const int SaveFormatVersion = 6; private const int MaximumSaveBytes = 67108864; private const float PositionUpdateInterval = 1f; private const float ClanChatInterval = 0.25f; private const float ClanPingInterval = 1f; private const float HudSelectionRefreshInterval = 2f; private const float HudFullResendInterval = 30f; private static Dictionary ClansById = new Dictionary(StringComparer.Ordinal); private static Dictionary ClansByName = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Dictionary PrimaryClanByPlayerId = new Dictionary(StringComparer.Ordinal); private static Dictionary GuestClanByPlayerId = new Dictionary(StringComparer.Ordinal); private static Dictionary PendingInvitesByTarget = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary LastPositionUpdateByPlayer = new Dictionary(StringComparer.Ordinal); private static readonly HashSet AnnouncedOnlinePlayerIds = new HashSet(StringComparer.Ordinal); private static readonly Dictionary HudCacheByViewerId = new Dictionary(StringComparer.Ordinal); private static bool _directoryInvalidationPending; private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly ISerializer YamlSerializer = new SerializerBuilder().DisableAliases().WithIndentedSequences().Build(); private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder().WithDuplicateKeyChecking().Build(); private static string? _loadedSaveFile; public static void Init() { _loadedSaveFile = null; LastPositionUpdateByPlayer.Clear(); AnnouncedOnlinePlayerIds.Clear(); HudCacheByViewerId.Clear(); _directoryInvalidationPending = false; SwapState(new RegistryData()); } public static void ResetOnlinePresence() { AnnouncedOnlinePlayerIds.Clear(); HudCacheByViewerId.Clear(); } private static bool IsRegistryWriteRequest(ClanRequestType type) { ClanDataRules.RequireEnum(type, "clan request type"); switch (type) { case ClanRequestType.RequestSnapshot: case ClanRequestType.SendClanChat: case ClanRequestType.SendClanPing: case ClanRequestType.UpdatePosition: case ClanRequestType.RequestDirectory: case ClanRequestType.RequestHud: return false; default: return true; } } public static void HandleRequest(ZNetPeer? peer, ClanRequest request) { //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Unknown result type (might be due to invalid IL or missing references) bool retryable; ClanPlayerRef clanPlayerRef = ClanIdentity.FromPeer(peer, out retryable); if (!clanPlayerRef.IsValid) { string status = (retryable ? ClanRpc.IdentityNotReadyStatus : ClanRpc.IdentityRejectedStatus); ClanOperationResultCode resultCode = (retryable ? ClanOperationResultCode.IdentityUnavailable : ClanOperationResultCode.IdentityRejected); if (!retryable) { ClanPlugin.ClanLogger.LogWarning((object)$"Rejected clan request from peer {peer?.m_uid}: character ownership mismatch."); } SendIdentityFailure(peer, request, status, resultCode); return; } if (!ClanRpc.TryPinPeerIdentity(peer, clanPlayerRef)) { ClanPlugin.ClanLogger.LogWarning((object)($"Rejected clan request from peer {peer?.m_uid}: character identity changed " + "during an active connection.")); SendIdentityFailure(peer, request, ClanRpc.IdentityRejectedStatus, ClanOperationResultCode.IdentityRejected); return; } bool flag = true; ClanOperationResultCode clanOperationResultCode = ClanOperationResultCode.None; bool flag2 = false; string text; try { bool flag3 = IsRegistryWriteRequest(request.Type); flag = request.Type == ClanRequestType.RequestSnapshot || flag3; EnsureLoaded(); RefreshPlayer(clanPlayerRef); ClanRecentPlayers.RememberPlayer(clanPlayerRef); flag2 = request.Type == ClanRequestType.RequestSnapshot && AnnouncedOnlinePlayerIds.Add(clanPlayerRef.Id); if (request.Type == ClanRequestType.RequestDirectory) { ClanRpc.SendDirectorySnapshot(peer, BuildDirectoryFor(clanPlayerRef, request.RequestId)); return; } if (request.Type == ClanRequestType.RequestHud) { ClanHudSnapshot clanHudSnapshot = BuildHudSnapshotFor(clanPlayerRef, peer, request.ClanId, request.HudSelectionRevision, request.HudStateRevision); if (clanHudSnapshot != null) { ClanRpc.SendHudSnapshot(peer, clanHudSnapshot); } return; } if (flag3 && !ClanRpc.ConsumeMutationRequest(clanPlayerRef)) { ClanRpc.SendSnapshot(peer, new ClanClientSnapshot { Status = ClanRpc.MutationRateLimitedStatus, ResponseRequestId = request.RequestId, ResponseResultCode = ClanOperationResultCode.RateLimited }); return; } if (request.Type == ClanRequestType.RenameClan) { OperationOutcome operationOutcome = RenameClan(clanPlayerRef, request.ClanId, request.ClanName); text = operationOutcome.Status; clanOperationResultCode = operationOutcome.Code; } else { text = request.Type switch { ClanRequestType.RequestSnapshot => "", ClanRequestType.CreateClan => CreateClan(clanPlayerRef, request.ClanName, request.Description, request.EmblemKey), ClanRequestType.Invite => Invite(clanPlayerRef, request.ClanId, request.TargetId), ClanRequestType.Apply => Apply(clanPlayerRef, request.ClanId), ClanRequestType.CancelApplication => CancelApplication(clanPlayerRef), ClanRequestType.AcceptInvite => AcceptInvite(clanPlayerRef, request.InviteId), ClanRequestType.DeclineInvite => DeclineInvite(clanPlayerRef, request.InviteId), ClanRequestType.AcceptApplication => ResolveApplication(clanPlayerRef, request.ClanId, request.TargetId, accept: true), ClanRequestType.RejectApplication => ResolveApplication(clanPlayerRef, request.ClanId, request.TargetId, accept: false), ClanRequestType.KickPlayer => Kick(clanPlayerRef, request.ClanId, request.TargetId), ClanRequestType.TransferLeadership => TransferLeadership(clanPlayerRef, request.ClanId, request.TargetId), ClanRequestType.SetRole => SetRole(clanPlayerRef, request.ClanId, request.TargetId, request.Role), ClanRequestType.UpdateClanProfile => UpdateClanProfile(clanPlayerRef, request.ClanId, request.ClanName, request.Description, request.EmblemKey), ClanRequestType.LeaveClan => Leave(clanPlayerRef, request.ClanId), ClanRequestType.SendClanChat => SendClanChat(clanPlayerRef, request.ClanId, request.Message), ClanRequestType.SendClanPing => SendClanPing(clanPlayerRef, request.ClanId, request.Position), ClanRequestType.UpdatePosition => UpdatePosition(clanPlayerRef, peer, request.ClanId, request.Position), _ => throw new InvalidDataException($"Unknown clan request type ({(int)request.Type})."), }; } } catch (InvalidDataException ex) { ClanPlugin.ClanLogger.LogWarning((object)$"Rejected clan request from {clanPlayerRef}: {ex.Message}"); if (request.Type == ClanRequestType.RenameClan) { text = ClanLocalization.EncodeStatus("clan_status_rename_validation_failed"); clanOperationResultCode = ClanOperationResultCode.Failed; } else { text = ClanLocalization.EncodeStatus("clan_status_invalid_request_server"); clanOperationResultCode = ClanOperationResultCode.Failed; } } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan request from {clanPlayerRef} failed: {arg}"); text = ClanLocalization.EncodeStatus("clan_status_request_failed"); clanOperationResultCode = ClanOperationResultCode.Failed; } FlushDirectoryInvalidation(); if (request.Type == ClanRequestType.RequestDirectory) { ClanRpc.SendDirectorySnapshot(peer, new ClanDirectorySnapshot { RequestId = request.RequestId, ResultCode = ((clanOperationResultCode == ClanOperationResultCode.None) ? ClanOperationResultCode.Failed : clanOperationResultCode), Status = text }); } else { if (!flag && string.IsNullOrWhiteSpace(text)) { return; } try { ClanRpc.SendSnapshot(peer, BuildSnapshotFor(clanPlayerRef, text, "", request.RequestId, clanOperationResultCode)); if (flag2) { BroadcastPresenceSnapshots(clanPlayerRef); } } catch (Exception arg2) { if (flag2) { AnnouncedOnlinePlayerIds.Remove(clanPlayerRef.Id); } ClanPlugin.ClanLogger.LogWarning((object)$"Failed to build clan snapshot for {clanPlayerRef}: {arg2}"); ClanRpc.SendSnapshot(peer, new ClanClientSnapshot { Status = ClanRpc.StateUnavailableStatus, ResponseRequestId = request.RequestId, ResponseResultCode = ClanOperationResultCode.Unavailable }); } } } public static ClanClientSnapshot BuildSnapshotFor(ClanPlayerRef player, string status = "", string forcedOfflinePlayerId = "", long responseRequestId = 0L, ClanOperationResultCode responseResultCode = ClanOperationResultCode.None) { ClanClientSnapshot clanClientSnapshot = new ClanClientSnapshot { Status = status, ResponseRequestId = responseRequestId, ResponseResultCode = responseResultCode }; if (!player.IsValid) { return clanClientSnapshot; } EnsureLoaded(); ClanState clanState = FindPrimaryClan(player); if (clanState != null && clanState.Members.TryGetValue(player.Id, out ClanMember value)) { clanClientSnapshot.PrimaryClanId = clanState.ClanId; clanClientSnapshot.PrimaryClanName = clanState.Name; clanClientSnapshot.PrimaryRole = value.Role; } ClanState clanState2 = FindGuestClan(player); if (clanState2 != null) { clanClientSnapshot.GuestClanId = clanState2.ClanId; clanClientSnapshot.GuestClanName = clanState2.Name; } ClanState activeClan = FindActiveClan(player); if (activeClan != null) { clanClientSnapshot.ClanId = activeClan.ClanId; clanClientSnapshot.ClanName = activeClan.Name; clanClientSnapshot.ClanDescription = activeClan.Description; clanClientSnapshot.ClanEmblemKey = activeClan.EmblemKey; if (activeClan.Members.TryGetValue(player.Id, out ClanMember value2)) { clanClientSnapshot.SelfRole = value2.Role; } HashSet onlinePlayerIds = new HashSet(from online in ClanIdentity.GetOnlinePlayerRefs() select online.Id, StringComparer.Ordinal); if (forcedOfflinePlayerId.Length != 0) { onlinePlayerIds.Remove(forcedOfflinePlayerId); } clanClientSnapshot.Roster.AddRange(from member in activeClan.Members.Values.OrderByDescending((ClanMember member) => ClanDataRules.GetRolePower(member.Role)).ThenBy((ClanMember member) => member.Player.Name, StringComparer.OrdinalIgnoreCase) select ToPlayerSummary(member, player, onlinePlayerIds.Contains(member.Player.Id) && FindActiveClan(member.Player) == activeClan)); if (clanClientSnapshot.CanModerate) { clanClientSnapshot.Applications.AddRange(activeClan.Applications.Values.OrderBy((ClanPlayerRef applicant) => applicant.Name, StringComparer.OrdinalIgnoreCase).Select(ToApplicationSummary)); } } if (PendingInvitesByTarget.TryGetValue(player.Id, out ClanInvite value3)) { if (!ClansById.TryGetValue(value3.ClanId, out ClanState value4)) { throw new InvalidDataException("Invite '" + value3.InviteId + "' references missing clan '" + value3.ClanId + "'."); } clanClientSnapshot.Invite = new ClanInviteSummary { InviteId = value3.InviteId, ClanId = value4.ClanId, ClanName = value4.Name, FromName = value3.FromName }; } ClanState clanState3 = ((clanState2 == null) ? FindApplicationClan(player.Id) : null); if (clanState3 != null && clanState3.Applications.ContainsKey(player.Id)) { clanClientSnapshot.OwnApplicationClanId = clanState3.ClanId; clanClientSnapshot.OwnApplicationClanName = clanState3.Name; } return clanClientSnapshot; } public static ClanHudSnapshot? BuildHudSnapshotFor(ClanPlayerRef viewer, ZNetPeer? viewerPeer, string requestedClanId, long knownSelectionRevision, long knownStateRevision) { if (!viewer.IsValid) { return null; } EnsureLoaded(); ClanState clanState = FindEffectiveClan(viewer, requestedClanId); if (clanState == null) { return null; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!HudCacheByViewerId.TryGetValue(viewer.Id, out HudViewerCache value)) { value = new HudViewerCache(); HudCacheByViewerId.Add(viewer.Id, value); } if (!StringComparer.Ordinal.Equals(value.ClanId, clanState.ClanId) || float.IsNegativeInfinity(value.LastSelectionRefreshTime) || realtimeSinceStartup < value.LastSelectionRefreshTime || realtimeSinceStartup - value.LastSelectionRefreshTime >= 2f) { RefreshHudSelection(value, clanState, viewer, viewerPeer, realtimeSinceStartup); } else { PruneInvalidHudSelection(value, clanState); } bool flag = value.SelectionRequiresFull; PendingHudUpdate pendingUpdate = value.PendingUpdate; if (pendingUpdate != null) { if (!StringComparer.Ordinal.Equals(pendingUpdate.Snapshot.ClanId, value.ClanId) || pendingUpdate.Snapshot.SelectionRevision != value.SelectionRevision) { value.PendingUpdate = null; flag = true; } else if (knownStateRevision == pendingUpdate.Snapshot.StateRevision && knownSelectionRevision == pendingUpdate.Snapshot.SelectionRevision) { CommitAcknowledgedHudUpdate(value, pendingUpdate); value.PendingUpdate = null; } else if (knownStateRevision == 0L) { if (pendingUpdate.Snapshot.ReplaceSelection) { return pendingUpdate.Snapshot; } value.PendingUpdate = null; flag = true; } else { if (knownStateRevision == value.AcknowledgedStateRevision && (pendingUpdate.Snapshot.ReplaceSelection || knownSelectionRevision == value.SelectionRevision)) { return pendingUpdate.Snapshot; } value.PendingUpdate = null; flag = true; } } if (knownSelectionRevision != value.SelectionRevision || knownStateRevision != value.AcknowledgedStateRevision || knownStateRevision == 0L) { flag = true; } if (float.IsNegativeInfinity(value.LastFullResendTime) || realtimeSinceStartup < value.LastFullResendTime || realtimeSinceStartup - value.LastFullResendTime >= 30f || value.AcknowledgedStateRevision == long.MaxValue) { flag = true; } if (!flag && !HasCompleteHudHealthBaseline(value)) { flag = true; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); ClanHudSnapshot clanHudSnapshot = new ClanHudSnapshot { ClanId = value.ClanId, SelectionRevision = value.SelectionRevision, StateRevision = NextHudRevision(value.AcknowledgedStateRevision), ReplaceSelection = flag }; foreach (HudSelectionEntry item in value.Selection) { HudHealthState hudHealthState = ReadHudHealth(item); if (flag || !value.AcknowledgedHealth.TryGetValue(item.PlayerId, out var value2) || !value2.Equals(hudHealthState)) { dictionary[item.PlayerId] = hudHealthState; clanHudSnapshot.Players.Add(new ClanHudPlayerSummary { PlayerId = item.PlayerId, HasHealth = hudHealthState.HasHealth, CurrentHealth = hudHealthState.CurrentHealth, MaxHealth = hudHealthState.MaxHealth }); } } if (!flag && clanHudSnapshot.Players.Count == 0) { return null; } value.PendingUpdate = new PendingHudUpdate(clanHudSnapshot, dictionary); if (flag) { value.SelectionRequiresFull = false; value.LastFullResendTime = realtimeSinceStartup; } return clanHudSnapshot; } private static void RefreshHudSelection(HudViewerCache cache, ClanState clan, ClanPlayerRef viewer, ZNetPeer? viewerPeer, float now) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) Vector3 val = viewerPeer?.m_refPos ?? (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); Player val2 = ((viewerPeer == null) ? Player.m_localPlayer : null); ClanPlayerRef clanPlayerRef = (((Object)(object)val2 != (Object)null) ? ClanPlayerRef.Local() : default(ClanPlayerRef)); bool num = viewerPeer != null || ((Object)(object)val2 != (Object)null && clanPlayerRef.IsValid && clanPlayerRef == viewer); ZDOID characterId = viewerPeer?.m_characterID ?? (((Object)(object)val2 != (Object)null) ? ((Character)val2).GetZDOID() : ZDOID.None); if (num && clan.Members.TryGetValue(viewer.Id, out ClanMember value) && FindActiveClan(value.Player) == clan) { dictionary[viewer.Id] = new HudCandidate(value, characterId, 0f); } if ((Object)(object)ZNet.instance != (Object)null) { foreach (PlayerInfo onlinePlayer in ClanIdentity.GetOnlinePlayers()) { ClanPlayerRef clanPlayerRef2 = ClanIdentity.FromPlayerInfo(onlinePlayer); if (clanPlayerRef2.IsValid && clan.Members.TryGetValue(clanPlayerRef2.Id, out ClanMember value2) && FindActiveClan(value2.Player) == clan) { Vector3 val3 = onlinePlayer.m_position - val; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; HudCandidate hudCandidate = new HudCandidate(value2, onlinePlayer.m_characterID, sqrMagnitude); if (!dictionary.TryGetValue(value2.Player.Id, out var value3) || hudCandidate.DistanceSquared < value3.DistanceSquared) { dictionary[value2.Player.Id] = hudCandidate; } } } } List selection = (from item in (from item in dictionary.Values.OrderBy((HudCandidate item) => item.DistanceSquared).ThenBy((HudCandidate item) => item.Member.Player.Id, StringComparer.Ordinal).Take(10) orderby ClanDataRules.GetRolePower(item.Member.Role) descending, item.DistanceSquared select item).ThenBy((HudCandidate item) => item.Member.Player.Name, StringComparer.OrdinalIgnoreCase).ThenBy((HudCandidate item) => item.Member.Player.Id, StringComparer.Ordinal) select new HudSelectionEntry(item.Member.Player, item.CharacterId)).ToList(); ReplaceHudSelection(cache, clan.ClanId, selection); cache.LastSelectionRefreshTime = now; } private static void PruneInvalidHudSelection(HudViewerCache cache, ClanState clan) { List list = null; for (int i = 0; i < cache.Selection.Count; i++) { HudSelectionEntry hudSelectionEntry = cache.Selection[i]; if (clan.Members.ContainsKey(hudSelectionEntry.PlayerId) && FindActiveClan(hudSelectionEntry.Player) == clan) { list?.Add(hudSelectionEntry); } else if (list == null) { list = cache.Selection.Take(i).ToList(); } } if (list != null) { ReplaceHudSelection(cache, clan.ClanId, list); } } private static void ReplaceHudSelection(HudViewerCache cache, string clanId, List selection) { bool flag = !StringComparer.Ordinal.Equals(cache.ClanId, clanId) || cache.Selection.Count != selection.Count; if (!flag) { for (int i = 0; i < selection.Count; i++) { if (!StringComparer.Ordinal.Equals(cache.Selection[i].PlayerId, selection[i].PlayerId)) { flag = true; break; } } } cache.ClanId = clanId; cache.Selection.Clear(); cache.Selection.AddRange(selection); if (flag) { cache.SelectionRevision = NextHudRevision(cache.SelectionRevision); cache.SelectionRequiresFull = true; cache.PendingUpdate = null; cache.AcknowledgedHealth.Clear(); cache.LastFullResendTime = float.NegativeInfinity; } } private static bool HasCompleteHudHealthBaseline(HudViewerCache cache) { if (cache.AcknowledgedHealth.Count != cache.Selection.Count) { return false; } return cache.Selection.All((HudSelectionEntry selected) => cache.AcknowledgedHealth.ContainsKey(selected.PlayerId)); } private static HudHealthState ReadHudHealth(HudSelectionEntry selected) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) float currentHealth; float maxHealth; bool flag = TryReadHudHealth(selected.CharacterId, selected.Player, out currentHealth, out maxHealth); return new HudHealthState(flag, flag ? currentHealth : 0f, flag ? maxHealth : 0f); } private static void CommitAcknowledgedHudUpdate(HudViewerCache cache, PendingHudUpdate pending) { if (pending.Snapshot.ReplaceSelection) { cache.AcknowledgedHealth.Clear(); } foreach (KeyValuePair item in pending.AbsoluteHealth) { cache.AcknowledgedHealth[item.Key] = item.Value; } cache.AcknowledgedStateRevision = pending.Snapshot.StateRevision; } private static long NextHudRevision(long revision) { if (revision < long.MaxValue) { return revision + 1; } return 1L; } private static void InvalidateHudCachesForDisconnectedPlayer(string playerId) { HudCacheByViewerId.Remove(playerId); foreach (HudViewerCache value in HudCacheByViewerId.Values) { if (!value.Selection.All((HudSelectionEntry selected) => !StringComparer.Ordinal.Equals(selected.PlayerId, playerId))) { List selection = value.Selection.Where((HudSelectionEntry selected) => !StringComparer.Ordinal.Equals(selected.PlayerId, playerId)).ToList(); ReplaceHudSelection(value, value.ClanId, selection); } } } private static bool TryReadHudHealth(ZDOID characterId, ClanPlayerRef expectedPlayer, out float currentHealth, out float maxHealth) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) currentHealth = 0f; maxHealth = 0f; if (((ZDOID)(ref characterId)).IsNone() || ZDOMan.instance == null) { return TryReadLocalHudHealth(expectedPlayer, out currentHealth, out maxHealth); } ZDO zDO = ZDOMan.instance.GetZDO(characterId); if (zDO == null || zDO.GetLong(ZDOVars.s_playerID, 0L) != expectedPlayer.CharacterPlayerId) { return TryReadLocalHudHealth(expectedPlayer, out currentHealth, out maxHealth); } maxHealth = zDO.GetFloat(ZDOVars.s_maxHealth, 0f); currentHealth = zDO.GetFloat(ZDOVars.s_health, maxHealth); return NormalizeHudHealth(ref currentHealth, ref maxHealth); } private static bool TryReadLocalHudHealth(ClanPlayerRef expectedPlayer, out float currentHealth, out float maxHealth) { currentHealth = 0f; maxHealth = 0f; if ((Object)(object)Player.m_localPlayer == (Object)null || expectedPlayer != ClanPlayerRef.Local()) { return false; } currentHealth = ((Character)Player.m_localPlayer).GetHealth(); maxHealth = ((Character)Player.m_localPlayer).GetMaxHealth(); return NormalizeHudHealth(ref currentHealth, ref maxHealth); } private static bool NormalizeHudHealth(ref float currentHealth, ref float maxHealth) { if (float.IsNaN(currentHealth) || float.IsInfinity(currentHealth) || float.IsNaN(maxHealth) || float.IsInfinity(maxHealth) || maxHealth <= 0f || maxHealth > 1000000f) { currentHealth = 0f; maxHealth = 0f; return false; } currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth); return true; } private static void SendIdentityFailure(ZNetPeer? peer, ClanRequest request, string status, ClanOperationResultCode resultCode) { if (request.Type == ClanRequestType.RequestDirectory) { ClanRpc.SendDirectorySnapshot(peer, new ClanDirectorySnapshot { RequestId = request.RequestId, ResultCode = resultCode, Status = status }); } else if (request.Type == ClanRequestType.RequestHud) { ClanRpc.SendHudSnapshot(peer, new ClanHudSnapshot()); } else { ClanRpc.SendSnapshot(peer, new ClanClientSnapshot { Status = status, ResponseRequestId = request.RequestId, ResponseResultCode = resultCode }); } } public static void NotifyPlayerDisconnected(ClanPlayerRef player) { if (!player.IsValid) { return; } ZNet instance = ZNet.instance; if (instance == null || !instance.IsServer() || ClanIdentity.GetOnlinePlayerRefs().Any((ClanPlayerRef online) => online == player)) { return; } ClanRecentPlayers.MarkPlayerOffline(player); AnnouncedOnlinePlayerIds.Remove(player.Id); InvalidateHudCachesForDisconnectedPlayer(player.Id); try { EnsureLoaded(); ClanState clanState = FindActiveClan(player); if (clanState == null) { return; } foreach (ClanMember value in clanState.Members.Values) { if (value.Player != player && FindActiveClan(value.Player) == clanState) { SendSnapshotUpdate(value.Player, "", player.Id); } } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)$"Failed to publish clan presence change for {player}: {ex.Message}"); } } public static ClanDirectorySnapshot BuildDirectoryFor(ClanPlayerRef viewer, long requestId, string status = "") { ClanDirectorySnapshot clanDirectorySnapshot = new ClanDirectorySnapshot { RequestId = ClanDataRules.RequireRequestId(requestId), ResultCode = ClanOperationResultCode.Success, Status = status }; if (!viewer.IsValid) { return clanDirectorySnapshot; } EnsureLoaded(); ClanState viewerPrimaryClan = FindPrimaryClan(viewer); ClanState viewerGuestClan = FindGuestClan(viewer); ClanState viewerApplicationClan = ((viewerGuestClan == null) ? FindApplicationClan(viewer.Id) : null); PendingInvitesByTarget.TryGetValue(viewer.Id, out ClanInvite viewerInvite); clanDirectorySnapshot.PublicClans.AddRange(from clan in (from clan in ClansById.Values orderby GetDirectoryClanRank(clan, viewerInvite, viewerGuestClan, viewerPrimaryClan, viewerApplicationClan), clan.CreationOrder descending select clan).ThenBy((ClanState clan) => clan.Name, StringComparer.OrdinalIgnoreCase).ThenBy((ClanState clan) => clan.ClanId, StringComparer.Ordinal) select new ClanPublicSummary { ClanId = clan.ClanId, Name = clan.Name, Description = clan.Description, EmblemKey = clan.EmblemKey, LeaderName = GetLeader(clan).Player.Name, MemberCount = clan.Members.Count }); ClanState viewerClan = FindActiveClan(viewer); ClanMember value; bool viewerCanModerate = viewerClan != null && viewerClan.Members.TryGetValue(viewer.Id, out value) && CanModerate(value); Dictionary applicationClanByPlayer = new Dictionary(StringComparer.Ordinal); foreach (ClanState value7 in ClansById.Values) { foreach (string key in value7.Applications.Keys) { applicationClanByPlayer[key] = value7; } } List list = ClanRecentPlayers.GetRecentPlayers().ToList(); if (list.All((ClanRecentPlayerEntry entry) => entry.Player != viewer)) { list.Insert(0, new ClanRecentPlayerEntry(viewer, isOnline: true, DateTime.UtcNow.Ticks)); } ClanState value6; foreach (ClanRecentPlayerEntry item in (from entry in list orderby entry.Player == viewer descending, viewerCanModerate && applicationClanByPlayer.TryGetValue(entry.Player.Id, out value6) && value6 == viewerClan descending, entry.IsOnline descending, entry.LastSeenUtcTicks descending select entry).ThenBy((ClanRecentPlayerEntry entry) => entry.Player.Name, StringComparer.OrdinalIgnoreCase).Take(1024)) { ClanPlayerRef player = item.Player; ClanState clanState = FindPrimaryClan(player); ClanState clanState2 = FindGuestClan(player); ClanState value2; ClanState clanState3 = ((clanState2 == null && applicationClanByPlayer.TryGetValue(player.Id, out value2)) ? value2 : null); bool flag = viewerCanModerate && clanState3 == viewerClan; bool flag2 = player == viewer; ClanInvite value3; ClanState value4; bool flag3 = PendingInvitesByTarget.TryGetValue(player.Id, out value3) && (flag2 || (viewerCanModerate && ClansById.TryGetValue(value3.ClanId, out value4) && viewerClan == value4)); ClanDirectoryPlayerState state; string clanName; ClanState value5; if (clanState != null || clanState2 != null) { state = ClanDirectoryPlayerState.Clan; clanName = (clanState2 ?? clanState).Name; } else if ((flag2 && clanState3 != null) || flag) { state = ClanDirectoryPlayerState.Pending; clanName = clanState3.Name; } else if (flag3 && ClansById.TryGetValue(value3.ClanId, out value5)) { state = ClanDirectoryPlayerState.Invited; clanName = value5.Name; } else { state = ClanDirectoryPlayerState.None; clanName = ""; } clanDirectorySnapshot.Players.Add(new ClanDirectoryPlayerSummary { PlayerId = player.Id, PlayerName = player.Name, State = state, ClanName = clanName, IsOnline = item.IsOnline, IsSelf = flag2, CanInvite = (viewerCanModerate && !flag2 && clanState2 == null && viewerClan != null && !viewerClan.Members.ContainsKey(player.Id) && clanState3 == null && !PendingInvitesByTarget.ContainsKey(player.Id)), CanResolveApplication = flag, LastSeenUtcTicks = item.LastSeenUtcTicks }); } return clanDirectorySnapshot; } private static int GetDirectoryClanRank(ClanState clan, ClanInvite? viewerInvite, ClanState? viewerGuestClan, ClanState? viewerPrimaryClan, ClanState? viewerApplicationClan) { if (viewerInvite != null && StringComparer.Ordinal.Equals(clan.ClanId, viewerInvite.ClanId)) { return 0; } if (clan == viewerGuestClan) { return 1; } if (clan == viewerPrimaryClan) { return 2; } if (clan != viewerApplicationClan) { return 4; } return 3; } private static ClanState? FindPrimaryClan(ClanPlayerRef player) { if (!player.IsValid) { return null; } if (!PrimaryClanByPlayerId.TryGetValue(player.Id, out ClanState value)) { return null; } return value; } internal static ClanWardAuthorizationResolution ResolveWardAuthorization(string platformId, long characterPlayerId, out string clanId, out string clanName) { clanId = ""; clanName = ""; ZNet instance = ZNet.instance; if (instance == null || !instance.IsServer()) { return ClanWardAuthorizationResolution.Unavailable; } try { EnsureLoaded(); } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Could not load the Clan registry for ward authorization: " + ex.Message)); return ClanWardAuthorizationResolution.Unavailable; } ClanPlayerRef clanPlayerRef = CreateIntegrationPlayerRef(platformId, characterPlayerId); if (!clanPlayerRef.IsValid) { return ClanWardAuthorizationResolution.ResolvedNoAuthorization; } if (!PrimaryClanByPlayerId.TryGetValue(clanPlayerRef.Id, out ClanState value)) { return ClanWardAuthorizationResolution.ResolvedNoAuthorization; } if (!value.Members.TryGetValue(clanPlayerRef.Id, out ClanMember value2)) { ClanPlugin.ClanLogger.LogWarning((object)("Primary Clan membership index for '" + clanPlayerRef.Id + "' is inconsistent.")); return ClanWardAuthorizationResolution.Unavailable; } if (value2.Role == ClanRole.Guest) { return ClanWardAuthorizationResolution.ResolvedNoAuthorization; } ClanRole role = value2.Role; if (role != ClanRole.Leader && role != ClanRole.Officer && role != ClanRole.Member) { return ClanWardAuthorizationResolution.Unavailable; } clanId = value.ClanId; clanName = value.Name; return ClanWardAuthorizationResolution.Authorized; } internal static ClanMembershipResolution ResolveMemberships(string platformId, long characterPlayerId, out string primaryClanId, out string primaryClanName, out string guestClanId, out string guestClanName) { primaryClanId = ""; primaryClanName = ""; guestClanId = ""; guestClanName = ""; ZNet instance = ZNet.instance; if (instance == null || !instance.IsServer()) { return ClanMembershipResolution.Unavailable; } try { EnsureLoaded(); ClanPlayerRef player = CreateIntegrationPlayerRef(platformId, characterPlayerId); if (!player.IsValid) { return ClanMembershipResolution.Unavailable; } if (!TryResolveIndexedMembership(player, PrimaryClanByPlayerId, expectGuest: false, out string clanId, out string clanName) || !TryResolveIndexedMembership(player, GuestClanByPlayerId, expectGuest: true, out string clanId2, out string clanName2)) { ClanPlugin.ClanLogger.LogWarning((object)("Clan membership indexes for '" + player.Id + "' are inconsistent.")); return ClanMembershipResolution.Unavailable; } primaryClanId = clanId; primaryClanName = clanName; guestClanId = clanId2; guestClanName = clanName2; return ClanMembershipResolution.Resolved; } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Could not resolve authoritative Clan memberships: " + ex.Message)); return ClanMembershipResolution.Unavailable; } } private static ClanPlayerRef CreateIntegrationPlayerRef(string platformId, long characterPlayerId) { string text = ClanDataRules.NormalizePlatformId(platformId); if (text.Length > 0 && ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var _)) { text = "Steam_" + text; } return new ClanPlayerRef(text, characterPlayerId, ""); } private static bool TryResolveIndexedMembership(ClanPlayerRef player, Dictionary membershipIndex, bool expectGuest, out string clanId, out string clanName) { clanId = ""; clanName = ""; if (!membershipIndex.TryGetValue(player.Id, out ClanState value)) { return true; } if (value == null || !ClansById.TryGetValue(value.ClanId, out ClanState value2) || value2 != value || !value.Members.TryGetValue(player.Id, out ClanMember value3) || value3 == null || !value3.Player.IsValid || !StringComparer.Ordinal.Equals(value3.Player.Id, player.Id)) { return false; } bool flag; if (expectGuest) { flag = value3.Role == ClanRole.Guest; } else { ClanRole role = value3.Role; bool flag2 = (uint)role <= 2u; flag = flag2; } if (!flag) { return false; } clanId = value.ClanId; clanName = value.Name; return true; } private static ClanState? FindGuestClan(ClanPlayerRef player) { if (!player.IsValid) { return null; } if (!GuestClanByPlayerId.TryGetValue(player.Id, out ClanState value)) { return null; } return value; } private static ClanState? FindEffectiveClan(ClanPlayerRef player, string requestedClanId) { string y = ClanDataRules.RequireClanId(requestedClanId); ClanState clanState = FindActiveClan(player); if (clanState == null || !StringComparer.Ordinal.Equals(clanState.ClanId, y)) { return null; } return clanState; } private static ClanState? FindActiveClan(ClanPlayerRef player) { if (!player.IsValid) { return null; } return FindGuestClan(player) ?? FindPrimaryClan(player); } private static string CreateClan(ClanPlayerRef actor, string requestedName, string requestedDescription, string requestedEmblemKey) { string text = ClanDataRules.RequireClanName(requestedName); string description = ClanDataRules.RequireClanDescription(requestedDescription); string text2 = ClanDataRules.RequireClanEmblemKey(requestedEmblemKey); if (!ClanEmoji.IsAvailableEmblemKey(text2)) { return ClanLocalization.EncodeStatus("clan_status_emblem_unavailable"); } if (FindPrimaryClan(actor) != null || FindGuestClan(actor) != null) { return ClanLocalization.EncodeStatus("clan_status_leave_affiliations_before_create"); } if (ClansByName.ContainsKey(text)) { return ClanLocalization.EncodeStatus("clan_status_name_exists", text); } if (ClansById.Count >= 1024) { return ClanLocalization.EncodeStatus("clan_status_server_clan_limit"); } string text3; do { text3 = Guid.NewGuid().ToString("N"); } while (ClansById.ContainsKey(text3)); ClanState clanState = new ClanState(text3) { CreationOrder = GetNextClanCreationOrder(), Name = text, Description = description, EmblemKey = text2 }; clanState.Members.Add(actor.Id, new ClanMember { Player = actor, Role = ClanRole.Leader }); ClansById.Add(clanState.ClanId, clanState); ClansByName.Add(clanState.Name, clanState); PrimaryClanByPlayerId.Add(actor.Id, clanState); Save(wardAuthorizationChanged: true); return ClanLocalization.EncodeStatus("clan_status_created", text); } private static long GetNextClanCreationOrder() { long num = 0L; foreach (ClanState value in ClansById.Values) { num = Math.Max(num, RequireClanCreationOrder(value.CreationOrder)); } if (num == long.MaxValue) { throw new InvalidDataException("Clan creation order is exhausted."); } return num + 1; } private static long RequireClanCreationOrder(long value, string fieldName = "clan creation order") { if (value <= 0) { throw new InvalidDataException(fieldName + " must be positive."); } return value; } private static string Invite(ClanPlayerRef actor, string requestedClanId, string targetId) { ClanState clanState = RequirePermission(actor, requestedClanId, CanModerate); if (clanState == null) { return ClanLocalization.EncodeStatus("clan_status_only_moderators_invite"); } if (!TryFindKnownPlayerById(targetId, out var player)) { return ClanLocalization.EncodeStatus("clan_status_player_not_recent"); } if (clanState.Members.ContainsKey(player.Id)) { return ClanLocalization.EncodeStatus("clan_status_player_already_connected", player.Name, clanState.Name); } if (FindGuestClan(player) != null) { return ClanLocalization.EncodeStatus("clan_status_player_leave_guest_before_join", player.Name); } ClanInvite value = new ClanInvite { ClanId = clanState.ClanId, FromName = actor.Name, Target = player }; if (!PendingInvitesByTarget.ContainsKey(player.Id) && PendingInvitesByTarget.Count >= 4096) { return ClanLocalization.EncodeStatus("clan_status_server_invite_limit"); } if (PendingInvitesByTarget.TryGetValue(player.Id, out ClanInvite value2) && StringComparer.Ordinal.Equals(value2.ClanId, clanState.ClanId)) { return ClanLocalization.EncodeStatus("clan_status_invite_already_pending", player.Name, clanState.Name); } PendingInvitesByTarget[player.Id] = value; Save(); SendSnapshotUpdate(player, ClanLocalization.EncodeStatus("clan_status_invited_you", actor.Name, clanState.Name)); return ClanLocalization.EncodeStatus("clan_status_invite_sent", player.Name); } private static string Apply(ClanPlayerRef actor, string requestedClanId) { string key = ClanDataRules.RequireClanId(requestedClanId); if (!ClansById.TryGetValue(key, out ClanState value)) { return ClanLocalization.EncodeStatus("clan_status_selected_clan_missing"); } if (value.Members.ContainsKey(actor.Id)) { return ClanLocalization.EncodeStatus("clan_status_already_connected", value.Name); } if (FindGuestClan(actor) != null) { return ClanLocalization.EncodeStatus("clan_status_leave_guest_before_apply"); } ClanState clanState = FindApplicationClan(actor.Id); if (clanState != value && value.Applications.Count >= 512) { return ClanLocalization.EncodeStatus("clan_status_application_limit", value.Name); } if (clanState == value) { return ClanLocalization.EncodeStatus("clan_status_application_already_pending", value.Name); } RemoveApplication(actor.Id); value.Applications[actor.Id] = actor; Save(); if (clanState != null && clanState != value) { SendModeratorSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_application_withdrawn_notice", actor.Name)); } SendModeratorSnapshots(value, ClanLocalization.EncodeStatus("clan_status_applied_notice", actor.Name)); return ClanLocalization.EncodeStatus("clan_status_applied", value.Name); } private static string CancelApplication(ClanPlayerRef actor) { ClanState clanState = RemoveApplication(actor.Id); if (clanState == null) { return ClanLocalization.EncodeStatus("clan_status_no_pending_application"); } Save(); SendModeratorSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_application_withdrawn_notice", actor.Name)); return ClanLocalization.EncodeStatus("clan_status_application_cancelled", clanState.Name); } private static string AcceptInvite(ClanPlayerRef actor, string inviteId) { if (!PendingInvitesByTarget.TryGetValue(actor.Id, out ClanInvite value) || !StringComparer.Ordinal.Equals(value.InviteId, inviteId)) { return ClanLocalization.EncodeStatus("clan_status_invite_missing"); } if (!ClansById.TryGetValue(value.ClanId, out ClanState value2)) { PendingInvitesByTarget.Remove(actor.Id); Save(); return ClanLocalization.EncodeStatus("clan_status_inviting_clan_missing"); } if (value2.Members.ContainsKey(actor.Id)) { return ClanLocalization.EncodeStatus("clan_status_already_connected", value2.Name); } if (FindGuestClan(actor) != null) { return ClanLocalization.EncodeStatus("clan_status_leave_guest_before_accept"); } if (value2.Members.Count >= 512) { return ClanLocalization.EncodeStatus("clan_status_member_limit", value2.Name); } ClanState clanState = FindActiveClan(actor); AddGuestToClan(value2, actor); PendingInvitesByTarget.Remove(actor.Id); ClanState clanState2 = RemoveApplication(actor.Id); Save(wardAuthorizationChanged: true); BroadcastSnapshots(value2, ClanLocalization.EncodeStatus("clan_status_joined_guest_notice", actor.Name), actor.Id); if (clanState != null && clanState != value2) { BroadcastSnapshots(clanState, "", actor.Id); } if (clanState2 != null && clanState2 != value2) { SendModeratorSnapshots(clanState2, ClanLocalization.EncodeStatus("clan_status_application_withdrawn_notice", actor.Name)); } return ClanLocalization.EncodeStatus("clan_status_joined_guest", value2.Name); } private static string DeclineInvite(ClanPlayerRef actor, string inviteId) { if (!PendingInvitesByTarget.TryGetValue(actor.Id, out ClanInvite value) || !StringComparer.Ordinal.Equals(value.InviteId, inviteId)) { return ClanLocalization.EncodeStatus("clan_status_invite_missing"); } PendingInvitesByTarget.Remove(actor.Id); Save(); ClanState value2; string text = (ClansById.TryGetValue(value.ClanId, out value2) ? value2.Name : value.ClanId); return ClanLocalization.EncodeStatus("clan_status_invite_declined", text); } private static string ResolveApplication(ClanPlayerRef actor, string requestedClanId, string applicantId, bool accept) { ClanState clanState = RequirePermission(actor, requestedClanId, CanModerate); if (clanState == null) { return ClanLocalization.EncodeStatus("clan_status_only_moderators_resolve"); } if (!clanState.Applications.TryGetValue(applicantId, out var value)) { return ClanLocalization.EncodeStatus("clan_status_application_missing"); } if (accept && clanState.Members.ContainsKey(value.Id)) { return ClanLocalization.EncodeStatus("clan_status_player_already_connected", value.Name, clanState.Name); } if (accept && FindGuestClan(value) != null) { return ClanLocalization.EncodeStatus("clan_status_applicant_leave_guest", value.Name); } if (accept && clanState.Members.Count >= 512) { return ClanLocalization.EncodeStatus("clan_status_member_limit", clanState.Name); } if (accept) { ClanState clanState2 = FindActiveClan(value); AddGuestToClan(clanState, value); clanState.Applications.Remove(applicantId); PendingInvitesByTarget.Remove(applicantId); Save(wardAuthorizationChanged: true); BroadcastSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_joined_guest_notice", value.Name), actor.Id); if (clanState2 != null && clanState2 != clanState) { BroadcastSnapshots(clanState2, "", value.Id); } return ClanLocalization.EncodeStatus("clan_status_application_accepted_guest", value.Name); } clanState.Applications.Remove(applicantId); Save(); SendSnapshotUpdate(value, ClanLocalization.EncodeStatus("clan_status_application_rejected_you", clanState.Name)); SendModeratorSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_application_rejected_notice", value.Name), actor.Id); return ClanLocalization.EncodeStatus("clan_status_application_rejected", value.Name); } private static string Kick(ClanPlayerRef actor, string requestedClanId, string targetId) { ClanState clanState = RequirePermission(actor, requestedClanId, CanModerate); if (clanState == null) { return ClanLocalization.EncodeStatus("clan_status_remove_unauthorized"); } if (!clanState.Members.TryGetValue(targetId, out ClanMember value)) { return ClanLocalization.EncodeStatus("clan_status_player_not_connected"); } if (value.Role == ClanRole.Leader) { return ClanLocalization.EncodeStatus("clan_status_transfer_before_remove_leader"); } if (!CanAffectMember(clanState, actor, value)) { return ClanLocalization.EncodeStatus("clan_status_cannot_remove_equal_role"); } ClanState clanState2 = FindActiveClan(value.Player); clanState.Members.Remove(value.Player.Id); RemoveMembershipIndex(clanState, value); if (FindPrimaryClan(value.Player) == null && FindGuestClan(value.Player) == null) { LastPositionUpdateByPlayer.Remove(value.Player.Id); } Save(wardAuthorizationChanged: true); SendSnapshotUpdate(value.Player, ClanLocalization.EncodeStatus("clan_status_removed_you", clanState.Name)); BroadcastSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_removed_notice", value.Player.Name), actor.Id); ClanState clanState3 = FindActiveClan(value.Player); if (clanState2 != clanState3 && clanState3 != null && clanState3 != clanState) { BroadcastSnapshots(clanState3, "", value.Player.Id); } return ClanLocalization.EncodeStatus("clan_status_removed", value.Player.Name); } private static string SetRole(ClanPlayerRef actor, string requestedClanId, string targetId, ClanRole requestedRole) { requestedRole = ClanDataRules.RequireAssignableRole(requestedRole, "clan role"); ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState == null || !clanState.Members.TryGetValue(actor.Id, out ClanMember value) || !CanModerate(value)) { return ClanLocalization.EncodeStatus("clan_status_only_moderators_roles"); } if (!clanState.Members.TryGetValue(targetId, out ClanMember value2)) { return ClanLocalization.EncodeStatus("clan_status_player_not_connected"); } if (value2.Player == actor) { return ClanLocalization.EncodeStatus("clan_status_cannot_change_own_role"); } if (value2.Role == ClanRole.Leader || requestedRole == ClanRole.Leader) { return ClanLocalization.EncodeStatus("clan_status_use_leadership_transfer"); } if (!CanAffectMember(clanState, actor, value2)) { return ClanLocalization.EncodeStatus("clan_status_cannot_change_equal_role"); } if (ClanDataRules.GetRolePower(requestedRole) >= ClanDataRules.GetRolePower(value.Role)) { return ClanLocalization.EncodeStatus("clan_status_cannot_assign_equal_role"); } if (value2.Role == requestedRole) { return EncodeRoleStatus("clan_status_player_already_role_", requestedRole, value2.Player.Name); } bool flag = value2.Role == ClanRole.Guest; bool flag2 = requestedRole == ClanRole.Guest; if (flag && !flag2) { if (PrimaryClanByPlayerId.TryGetValue(value2.Player.Id, out ClanState value3)) { if (value3 != clanState) { return ClanLocalization.EncodeStatus("clan_status_player_has_primary_clan", value2.Player.Name); } throw new InvalidDataException("Player '" + value2.Player.Id + "' is indexed as both Guest and primary in the same clan."); } if (!GuestClanByPlayerId.TryGetValue(value2.Player.Id, out ClanState value4) || value4 != clanState) { throw new InvalidDataException("Guest membership index for '" + value2.Player.Id + "' is inconsistent."); } GuestClanByPlayerId.Remove(value2.Player.Id); PrimaryClanByPlayerId.Add(value2.Player.Id, clanState); } else if (!flag && flag2) { if (GuestClanByPlayerId.TryGetValue(value2.Player.Id, out ClanState value5)) { if (value5 != clanState) { return ClanLocalization.EncodeStatus("clan_status_player_has_guest_clan", value2.Player.Name); } throw new InvalidDataException("Player '" + value2.Player.Id + "' is indexed as both primary and Guest in the same clan."); } if (FindApplicationClan(value2.Player.Id) != null || PendingInvitesByTarget.ContainsKey(value2.Player.Id)) { return ClanLocalization.EncodeStatus("clan_status_player_resolve_pending_guest", value2.Player.Name); } if (!PrimaryClanByPlayerId.TryGetValue(value2.Player.Id, out ClanState value6) || value6 != clanState) { throw new InvalidDataException("Primary membership index for '" + value2.Player.Id + "' is inconsistent."); } PrimaryClanByPlayerId.Remove(value2.Player.Id); GuestClanByPlayerId.Add(value2.Player.Id, clanState); } value2.Role = requestedRole; Save(wardAuthorizationChanged: true); if (FindActiveClan(value2.Player) != clanState) { SendSnapshotUpdate(value2.Player, EncodeRoleStatus("clan_status_your_role_changed_", requestedRole, clanState.Name)); } BroadcastSnapshots(clanState, EncodeRoleStatus("clan_status_role_changed_notice_", requestedRole, value2.Player.Name), actor.Id); return EncodeRoleStatus("clan_status_role_set_", requestedRole, value2.Player.Name); } private static string TransferLeadership(ClanPlayerRef actor, string requestedClanId, string targetId) { ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState != null && !clanState.IsLeader(actor)) { clanState = null; } if (clanState == null) { return ClanLocalization.EncodeStatus("clan_status_only_leader_transfer"); } if (clanState.Members.TryGetValue(targetId, out ClanMember value)) { ClanRole role = value.Role; if (role == ClanRole.Officer || role == ClanRole.Member) { if (value.Player == actor) { return ClanLocalization.EncodeStatus("clan_status_already_leader"); } if (FindActiveClan(value.Player) != clanState) { return ClanLocalization.EncodeStatus("clan_status_leave_guest_before_leadership", value.Player.Name); } clanState.Members[actor.Id].Role = ClanRole.Member; value.Role = ClanRole.Leader; Save(wardAuthorizationChanged: true); BroadcastSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_now_leader_notice", value.Player.Name), actor.Id); return ClanLocalization.EncodeStatus("clan_status_leadership_transferred", value.Player.Name); } } return ClanLocalization.EncodeStatus("clan_status_transfer_target_role"); } private static string UpdateClanProfile(ClanPlayerRef actor, string requestedClanId, string requestedName, string requestedDescription, string requestedEmblemKey) { string y = ClanDataRules.RequireClanId(requestedClanId); string clanName = ClanDataRules.RequireClanName(requestedName); string description = ClanDataRules.RequireClanDescription(requestedDescription); string text = ClanDataRules.RequireClanEmblemKey(requestedEmblemKey); if (!ClanEmoji.IsAvailableEmblemKey(text)) { return ClanLocalization.EncodeStatus("clan_status_emblem_unavailable"); } ClanState clanState = FindActiveClan(actor); if (clanState == null || !clanState.IsLeader(actor) || !StringComparer.Ordinal.Equals(clanState.ClanId, y)) { return ClanLocalization.EncodeStatus("clan_status_only_leader_update_profile"); } return ApplyClanProfileChange(clanState, clanName, description, text, actor.Id).Status; } private static OperationOutcome RenameClan(ClanPlayerRef actor, string requestedClanId, string requestedName) { string y = ClanDataRules.RequireClanId(requestedClanId); string text = ClanDataRules.RequireClanName(requestedName); ClanState clanState = FindActiveClan(actor); if (clanState == null || !StringComparer.Ordinal.Equals(clanState.ClanId, y)) { return new OperationOutcome(ClanOperationResultCode.ClanChanged, ClanLocalization.EncodeStatus("clan_status_clan_changed_before_rename")); } if (!clanState.IsLeader(actor)) { return new OperationOutcome(ClanOperationResultCode.Unauthorized, ClanLocalization.EncodeStatus("clan_status_only_leader_rename")); } OperationOutcome operationOutcome = ApplyClanProfileChange(clanState, text, clanState.Description, clanState.EmblemKey, actor.Id); return operationOutcome.Code switch { ClanOperationResultCode.Success => new OperationOutcome(ClanOperationResultCode.Success, ClanLocalization.EncodeStatus("clan_status_renamed", text)), ClanOperationResultCode.Unchanged => new OperationOutcome(ClanOperationResultCode.Unchanged, ClanLocalization.EncodeStatus("clan_status_name_unchanged")), _ => operationOutcome, }; } private static OperationOutcome ApplyClanProfileChange(ClanState clan, string clanName, string description, string emblemKey, string actorId) { if (ClansByName.TryGetValue(clanName, out ClanState value) && value != clan) { return new OperationOutcome(ClanOperationResultCode.NameTaken, ClanLocalization.EncodeStatus("clan_status_name_exists", clanName)); } if (StringComparer.Ordinal.Equals(clan.Name, clanName) && StringComparer.Ordinal.Equals(clan.Description, description) && StringComparer.Ordinal.Equals(clan.EmblemKey, emblemKey)) { return new OperationOutcome(ClanOperationResultCode.Unchanged, ClanLocalization.EncodeStatus("clan_status_profile_unchanged")); } NotifyProfileCommitted(CommitClanProfile(clan, clanName, description, emblemKey), actorId); return new OperationOutcome(ClanOperationResultCode.Success, ClanLocalization.EncodeStatus("clan_status_profile_updated")); } private static ClanState CommitClanProfile(ClanState clan, string clanName, string description, string emblemKey) { if (!ClansById.TryGetValue(clan.ClanId, out ClanState value) || value != clan || !ClansByName.TryGetValue(clan.Name, out ClanState value2) || value2 != clan) { throw new InvalidOperationException("Clan '" + clan.ClanId + "' is inconsistent with the registry indexes."); } string text = ResolveSaveFile(); if (!string.Equals(_loadedSaveFile, text, StringComparison.Ordinal)) { throw new InvalidOperationException("The clan registry storage changed while clan state was being updated."); } ValidateClanIndexes(); YamlRegistryDocument yamlRegistryDocument = CreateSaveDocument(); YamlClanDto? obj = yamlRegistryDocument.Clans?.SingleOrDefault((YamlClanDto candidate) => candidate != null && StringComparer.Ordinal.Equals(candidate.ClanId, clan.ClanId)) ?? throw new InvalidOperationException("Clan '" + clan.ClanId + "' is missing from the save candidate."); obj.Name = ClanDataRules.RequireClanName(clanName); obj.Description = ClanDataRules.RequireClanDescription(description); obj.EmblemKey = ClanDataRules.RequireClanEmblemKey(emblemKey); byte[] array = SerializeSave(yamlRegistryDocument); if (array.Length > 67108864) { throw new InvalidDataException($"Clan save exceeds the {67108864}-byte limit."); } RegistryData registryData = ParseSave(array); PreserveRuntimeMemberState(registryData); try { WriteAtomically(text, array); } catch { RecoverPersistedStateAfterSaveFailure(); throw; } SwapState(registryData); _directoryInvalidationPending = true; ClanApi.NotifyRegistryChanged(); return ClansById[clan.ClanId]; } private static string Leave(ClanPlayerRef actor, string requestedClanId) { ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState == null || !clanState.Members.TryGetValue(actor.Id, out ClanMember value)) { return ClanLocalization.EncodeStatus("clan_status_not_connected_to_selected_clan"); } if (clanState.IsLeader(actor) && clanState.Members.Values.Count((ClanMember member) => member.Role != ClanRole.Guest) > 1) { return ClanLocalization.EncodeStatus("clan_status_transfer_before_leaving"); } clanState.Members.Remove(actor.Id); RemoveMembershipIndex(clanState, value); if (FindPrimaryClan(actor) == null && FindGuestClan(actor) == null) { LastPositionUpdateByPlayer.Remove(actor.Id); } if (!clanState.Members.Values.Any((ClanMember member) => member.Role != ClanRole.Guest)) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); HashSet hashSet = new HashSet(); foreach (ClanMember value2 in clanState.Members.Values) { dictionary[value2.Player.Id] = value2.Player; ClanState clanState2 = FindPrimaryClan(value2.Player); if (clanState2 != null && clanState2 != clanState) { hashSet.Add(clanState2); } } foreach (ClanPlayerRef value3 in clanState.Applications.Values) { dictionary[value3.Id] = value3; } foreach (ClanInvite value4 in PendingInvitesByTarget.Values) { if (string.Equals(value4.ClanId, clanState.ClanId, StringComparison.Ordinal)) { dictionary[value4.Target.Id] = value4.Target; } } RemoveClan(clanState); Save(wardAuthorizationChanged: true); foreach (ClanPlayerRef value5 in dictionary.Values) { if (value5 != actor) { SendSnapshotUpdate(value5, ClanLocalization.EncodeStatus("clan_status_disbanded_notice", clanState.Name)); } } foreach (ClanState item in hashSet) { BroadcastSnapshots(item, ""); } return ClanLocalization.EncodeStatus("clan_status_disbanded", clanState.Name); } Save(wardAuthorizationChanged: true); BroadcastSnapshots(clanState, ClanLocalization.EncodeStatus("clan_status_left_notice", actor.Name, clanState.Name)); ClanState clanState3 = FindActiveClan(actor); if (clanState3 != null && clanState3 != clanState) { BroadcastSnapshots(clanState3, "", actor.Id); } if (value.Role != ClanRole.Guest) { return ClanLocalization.EncodeStatus("clan_status_left", clanState.Name); } return ClanLocalization.EncodeStatus("clan_status_left_guest", clanState.Name); } private static string SendClanChat(ClanPlayerRef actor, string requestedClanId, string requestedMessage) { ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState == null || !clanState.Members.TryGetValue(actor.Id, out ClanMember value)) { return ClanLocalization.EncodeStatus("clan_status_not_connected_to_selected_clan"); } string message = ClanDataRules.RequireText(requestedMessage, 400, "clan chat message", allowEmpty: false); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= value.LastClanChatTime && realtimeSinceStartup - value.LastClanChatTime < 0.25f) { return ""; } value.LastClanChatTime = realtimeSinceStartup; ClanRpc.BroadcastChat(clanState, actor.Name, message); return ""; } private static string SendClanPing(ClanPlayerRef actor, string requestedClanId, Vector3 position) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState == null || !clanState.Members.TryGetValue(actor.Id, out ClanMember value)) { return ClanLocalization.EncodeStatus("clan_status_not_connected_to_selected_clan"); } ClanDataRules.RequireFiniteVector(position, "ping position"); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= value.LastClanPingTime && realtimeSinceStartup - value.LastClanPingTime < 1f) { return ""; } value.LastClanPingTime = realtimeSinceStartup; ClanRpc.BroadcastPing(clanState, actor, position); return ""; } private static string UpdatePosition(ClanPlayerRef actor, ZNetPeer? peer, string requestedClanId, Vector3 requestedPosition) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_0081: Unknown result type (might be due to invalid IL or missing references) if (!ClanPlugin.ShareClanPositions.Value.IsOn()) { return ""; } ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState == null) { return ""; } Vector3 val = peer?.m_refPos ?? requestedPosition; ClanDataRules.RequireFiniteVector(val, "player position"); float realtimeSinceStartup = Time.realtimeSinceStartup; if (LastPositionUpdateByPlayer.TryGetValue(actor.Id, out var value) && realtimeSinceStartup >= value && realtimeSinceStartup - value < 1f) { return ""; } LastPositionUpdateByPlayer[actor.Id] = realtimeSinceStartup; ClanRpc.BroadcastPosition(clanState, actor, val); return ""; } internal static bool IsEffectiveMember(ClanState clan, ClanMember member) { return FindActiveClan(member.Player) == clan; } private static ClanState? RequirePermission(ClanPlayerRef actor, string requestedClanId, Func canUse) { ClanState clanState = FindEffectiveClan(actor, requestedClanId); if (clanState == null || !clanState.Members.TryGetValue(actor.Id, out ClanMember value)) { return null; } if (!canUse(value)) { return null; } return clanState; } private static bool CanModerate(ClanMember member) { ClanRole role = member.Role; if ((uint)role <= 1u) { return true; } return false; } private static string EncodeRoleStatus(string keyPrefix, ClanRole role, params object[] arguments) { return ClanLocalization.EncodeStatus(keyPrefix + role switch { ClanRole.Leader => "leader", ClanRole.Officer => "officer", ClanRole.Member => "member", ClanRole.Guest => "guest", _ => throw new InvalidDataException("Clan role is unsupported."), }, arguments); } private static bool CanAffectMember(ClanState clan, ClanPlayerRef actor, ClanMember target) { if (clan.Members.TryGetValue(actor.Id, out ClanMember value)) { return ClanDataRules.GetRolePower(value.Role) > ClanDataRules.GetRolePower(target.Role); } return false; } private static void AddGuestToClan(ClanState clan, ClanPlayerRef player) { if (!player.IsValid) { throw new InvalidDataException("Cannot add an unidentified player to a clan."); } if (clan.Members.ContainsKey(player.Id)) { throw new InvalidOperationException("Player is already connected to '" + clan.Name + "'."); } if (GuestClanByPlayerId.ContainsKey(player.Id)) { throw new InvalidOperationException("Player must leave their current guest clan before joining another as Guest."); } if (clan.Members.Count >= 512) { throw new InvalidOperationException("Clan '" + clan.Name + "' has reached the member limit."); } ClanMember value = new ClanMember { Player = player, Role = ClanRole.Guest }; clan.Members.Add(player.Id, value); GuestClanByPlayerId.Add(player.Id, clan); } private static void RemoveMembershipIndex(ClanState clan, ClanMember member) { Dictionary dictionary = ((member.Role == ClanRole.Guest) ? GuestClanByPlayerId : PrimaryClanByPlayerId); if (!dictionary.TryGetValue(member.Player.Id, out var value) || value != clan) { throw new InvalidDataException("Membership index for '" + member.Player.Id + "' in clan '" + clan.ClanId + "' is inconsistent."); } dictionary.Remove(member.Player.Id); } private static ClanState? RemoveApplication(string playerId) { ClanState clanState = FindApplicationClan(playerId); if (clanState == null) { return null; } clanState.Applications.Remove(playerId); return clanState; } private static ClanState? FindApplicationClan(string playerId) { return ClansById.Values.FirstOrDefault((ClanState clan) => clan.Applications.ContainsKey(playerId)); } private static void RemoveClan(ClanState clan) { ClansById.Remove(clan.ClanId); ClansByName.Remove(clan.Name); foreach (ClanMember value in clan.Members.Values) { RemoveMembershipIndex(clan, value); if (FindPrimaryClan(value.Player) == null && FindGuestClan(value.Player) == null) { LastPositionUpdateByPlayer.Remove(value.Player.Id); } } string[] array = (from pair in PendingInvitesByTarget where StringComparer.Ordinal.Equals(pair.Value.ClanId, clan.ClanId) select pair.Key).ToArray(); foreach (string key in array) { PendingInvitesByTarget.Remove(key); } } private static ClanMember GetLeader(ClanState clan) { return clan.Members.Values.FirstOrDefault((ClanMember member) => member.Role == ClanRole.Leader) ?? throw new InvalidDataException("Clan '" + clan.Name + "' has no leader."); } private static void BroadcastSnapshots(ClanState clan, string status, string excludedPlayerId = "") { foreach (ClanMember value in clan.Members.Values) { if (!StringComparer.Ordinal.Equals(value.Player.Id, excludedPlayerId) && FindActiveClan(value.Player) == clan) { SendSnapshotUpdate(value.Player, status); } } } private static void BroadcastPresenceSnapshots(ClanPlayerRef connectedPlayer) { ClanState clanState = FindActiveClan(connectedPlayer); if (clanState != null) { BroadcastSnapshots(clanState, "", connectedPlayer.Id); } } private static void BroadcastProfileSnapshots(ClanState clan, string excludedPlayerId) { HashSet hashSet = new HashSet(StringComparer.Ordinal) { excludedPlayerId }; foreach (ClanMember value in clan.Members.Values) { if (hashSet.Add(value.Player.Id)) { SendSnapshotUpdate(value.Player, ClanLocalization.EncodeStatus("clan_status_profile_updated")); } } foreach (ClanPlayerRef value2 in clan.Applications.Values) { if (hashSet.Add(value2.Id)) { SendSnapshotUpdate(value2, ClanLocalization.EncodeStatus("clan_status_profile_updated")); } } foreach (ClanInvite value3 in PendingInvitesByTarget.Values) { if (StringComparer.Ordinal.Equals(value3.ClanId, clan.ClanId) && hashSet.Add(value3.Target.Id)) { SendSnapshotUpdate(value3.Target, ClanLocalization.EncodeStatus("clan_status_profile_updated")); } } } private static void NotifyProfileCommitted(ClanState clan, string excludedPlayerId) { try { BroadcastProfileSnapshots(clan, excludedPlayerId); } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Clan profile '" + clan.ClanId + "' was committed, but notifying clients failed: " + ex)); } } private static void FlushDirectoryInvalidation() { if (!_directoryInvalidationPending) { return; } _directoryInvalidationPending = false; try { ClanRpc.BroadcastDirectoryInvalidation(); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan data was saved, but invalidating client directories failed: {arg}"); } } private static void SendModeratorSnapshots(ClanState clan, string status, string excludedPlayerId = "") { foreach (ClanMember value in clan.Members.Values) { if (CanModerate(value) && FindActiveClan(value.Player) == clan && !StringComparer.Ordinal.Equals(value.Player.Id, excludedPlayerId)) { SendSnapshotUpdate(value.Player, status); } } } private static void SendSnapshotUpdate(ClanPlayerRef player, string status, string forcedOfflinePlayerId = "") { try { ZNetPeer val = ClanIdentity.FindPeer(player); bool flag = (Object)(object)Player.m_localPlayer != (Object)null && player == ClanPlayerRef.Local(); if (val != null || flag) { ClanRpc.SendSnapshot(val, BuildSnapshotFor(player, status, forcedOfflinePlayerId, 0L)); } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)$"Failed to update clan snapshot for {player}: {ex.Message}"); } } private static void RefreshPlayer(ClanPlayerRef player) { if (player.IsValid) { if (PrimaryClanByPlayerId.TryGetValue(player.Id, out ClanState value) && value.Members.TryGetValue(player.Id, out ClanMember value2)) { value2.Player = player; } if (GuestClanByPlayerId.TryGetValue(player.Id, out ClanState value3) && value3.Members.TryGetValue(player.Id, out ClanMember value4)) { value4.Player = player; } if (PendingInvitesByTarget.TryGetValue(player.Id, out ClanInvite value5)) { value5.Target = player; } ClanState clanState = (GuestClanByPlayerId.ContainsKey(player.Id) ? null : FindApplicationClan(player.Id)); if (clanState != null && clanState.Applications.ContainsKey(player.Id)) { clanState.Applications[player.Id] = player; } } } private static bool TryFindKnownPlayerById(string targetId, out ClanPlayerRef player) { string text = ClanDataRules.RequirePlayerKey(targetId, "target player key"); foreach (ClanPlayerRef onlinePlayerRef in ClanIdentity.GetOnlinePlayerRefs()) { if (StringComparer.Ordinal.Equals(onlinePlayerRef.Id, text)) { player = onlinePlayerRef; return true; } } return ClanRecentPlayers.TryGetPlayer(text, out player); } private static ClanPlayerSummary ToPlayerSummary(ClanMember member, ClanPlayerRef viewer, bool isOnline) { return new ClanPlayerSummary { Player = member.Player, Role = member.Role, IsSelf = (member.Player == viewer), IsOnline = isOnline }; } private static ClanApplicationSummary ToApplicationSummary(ClanPlayerRef applicant) { return new ClanApplicationSummary { PlayerId = applicant.Id, PlayerName = applicant.Name }; } private static void EnsureLoaded() { string text = ResolveSaveFile(); if (!string.Equals(_loadedSaveFile, text, StringComparison.Ordinal)) { SwapState(Load(text)); LastPositionUpdateByPlayer.Clear(); AnnouncedOnlinePlayerIds.Clear(); HudCacheByViewerId.Clear(); _loadedSaveFile = text; } } private static string ResolveSaveFile() { return Path.Combine(ClanPlugin.DataDirectory, "clans.yml"); } private static RegistryData Load(string saveFile) { string text = saveFile + ".bak"; if (TryLoadSave(saveFile, out byte[] _, out RegistryData data, out Exception invalidContent)) { ClanPlugin.ClanLogger.LogInfo((object)$"Loaded {data.ClansById.Count} clans from {saveFile}."); return data; } if (invalidContent == null) { if (!TryLoadSave(text, out byte[] bytes2, out RegistryData data2, out Exception invalidContent2)) { if (invalidContent2 != null) { string text2 = QuarantineUnsupportedSave(text); ClanPlugin.ClanLogger.LogWarning((object)("Clan save was missing and its backup was unsupported or invalid. The backup was moved to " + text2 + "; the clan registry will start empty: " + invalidContent2.Message)); } return new RegistryData(); } WriteAtomically(saveFile, bytes2); ClanPlugin.ClanLogger.LogWarning((object)($"Clan save was missing. Restored {data2.ClansById.Count} clans from " + "the validated backup " + text + " to " + saveFile + ".")); return data2; } byte[] bytes3 = null; RegistryData data3 = null; Exception invalidContent3 = null; if (TryLoadSave(text, out bytes3, out data3, out invalidContent3)) { string text3 = QuarantineUnsupportedSave(saveFile); WriteAtomically(saveFile, bytes3); ClanPlugin.ClanLogger.LogWarning((object)("Clan save was unsupported or invalid and was moved to " + text3 + ": " + $"{invalidContent.Message} Restored {data3.ClansById.Count} clans from " + "the validated same-version backup " + text + ".")); return data3; } string text4 = QuarantineUnsupportedSave(saveFile); if (invalidContent3 != null) { string text5 = QuarantineUnsupportedSave(text); ClanPlugin.ClanLogger.LogWarning((object)("Clan save was unsupported or invalid and was moved to " + text4 + ": " + invalidContent.Message + " Its backup was also unsupported or invalid and was moved to " + text5 + ": " + invalidContent3.Message + " The clan registry will start empty.")); } else { ClanPlugin.ClanLogger.LogWarning((object)("Clan save was unsupported or invalid and was moved to " + text4 + ": " + invalidContent.Message + " No backup was available; the clan registry will start empty.")); } return new RegistryData(); } private static bool TryLoadSave(string saveFile, out byte[]? bytes, out RegistryData? data, out Exception? invalidContent) { bytes = null; data = null; invalidContent = null; try { bytes = ReadSaveBytes(saveFile); data = ParseSave(bytes); return true; } catch (FileNotFoundException) { return false; } catch (DirectoryNotFoundException) { return false; } catch (Exception ex3) when (IsInvalidSaveContent(ex3)) { invalidContent = ex3; return false; } } private static bool IsInvalidSaveContent(Exception error) { if (error is InvalidDataException || error is DecoderFallbackException || error is YamlException) { return true; } return false; } private static RegistryData ParseSave(byte[] bytes) { string input = DecodeStrictUtf8(bytes); YamlRegistryDocument yamlRegistryDocument = YamlDeserializer.Deserialize(input); if (yamlRegistryDocument == null) { throw new InvalidDataException("Clan save is empty."); } if (yamlRegistryDocument.FormatVersion != 6) { throw new InvalidDataException($"Save format version {yamlRegistryDocument.FormatVersion} is unsupported; " + $"only version {6} is accepted."); } if (yamlRegistryDocument.Clans == null) { throw new InvalidDataException("Clan save is missing the clans sequence."); } if (yamlRegistryDocument.PendingInvites == null) { throw new InvalidDataException("Clan save is missing the pending_invites sequence."); } RegistryData registryData = new RegistryData(); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); ClanDataRules.RequireCount(yamlRegistryDocument.Clans.Count, 1024, "clan"); foreach (YamlClanDto clan in yamlRegistryDocument.Clans) { ClanState clanState = ReadClan(clan); if (!hashSet.Add(clanState.CreationOrder)) { throw new InvalidDataException($"Duplicate clan creation order '{clanState.CreationOrder}'."); } if (registryData.ClansById.ContainsKey(clanState.ClanId)) { throw new InvalidDataException("Duplicate clan id '" + clanState.ClanId + "'."); } if (registryData.ClansByName.ContainsKey(clanState.Name)) { throw new InvalidDataException("Duplicate clan name '" + clanState.Name + "'."); } registryData.ClansById.Add(clanState.ClanId, clanState); registryData.ClansByName.Add(clanState.Name, clanState); foreach (KeyValuePair member in clanState.Members) { Dictionary dictionary2 = ((member.Value.Role == ClanRole.Guest) ? registryData.GuestClanByPlayerId : registryData.PrimaryClanByPlayerId); if (dictionary2.ContainsKey(member.Key)) { string text = ((member.Value.Role == ClanRole.Guest) ? "Guest" : "primary"); throw new InvalidDataException("Player '" + member.Key + "' belongs to more than one " + text + " clan."); } dictionary2.Add(member.Key, clanState); } foreach (string key in clanState.Applications.Keys) { if (!hashSet2.Add(key)) { throw new InvalidDataException("Player '" + key + "' has more than one application."); } dictionary.Add(key, clanState); } } foreach (string item in hashSet2) { ClanState clanState2 = dictionary[item]; if (registryData.GuestClanByPlayerId.ContainsKey(item)) { throw new InvalidDataException("Player '" + item + "' cannot have a Guest clan and a pending application."); } if (clanState2.Members.ContainsKey(item)) { throw new InvalidDataException("Player '" + item + "' cannot apply to a clan they already belong to."); } } ClanDataRules.RequireCount(yamlRegistryDocument.PendingInvites.Count, 4096, "invite"); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); foreach (YamlInviteDto pendingInvite in yamlRegistryDocument.PendingInvites) { ClanInvite clanInvite = ReadInvite(pendingInvite); if (!hashSet3.Add(clanInvite.InviteId)) { throw new InvalidDataException("Duplicate invite id '" + clanInvite.InviteId + "'."); } if (!registryData.ClansById.TryGetValue(clanInvite.ClanId, out ClanState value)) { throw new InvalidDataException("Invite references missing clan '" + clanInvite.ClanId + "'."); } if (registryData.GuestClanByPlayerId.ContainsKey(clanInvite.Target.Id)) { throw new InvalidDataException("Invite target '" + clanInvite.Target.Id + "' already has a Guest clan."); } if (value.Members.ContainsKey(clanInvite.Target.Id)) { throw new InvalidDataException("Invite target '" + clanInvite.Target.Id + "' already belongs to the inviting clan."); } if (registryData.PendingInvitesByTarget.ContainsKey(clanInvite.Target.Id)) { throw new InvalidDataException("Player '" + clanInvite.Target.Id + "' has more than one pending invite."); } registryData.PendingInvitesByTarget.Add(clanInvite.Target.Id, clanInvite); } return registryData; } private static string DecodeStrictUtf8(byte[] bytes) { int num = ((bytes.Length >= 3 && bytes[0] == 239 && bytes[1] == 187 && bytes[2] == 191) ? 3 : 0); return StrictUtf8.GetString(bytes, num, bytes.Length - num); } private static ClanState ReadClan(YamlClanDto? source) { if (source == null) { throw new InvalidDataException("Clan save contains a null clan entry."); } ClanState clanState = new ClanState(ClanDataRules.RequireClanId(source.ClanId)) { CreationOrder = RequireClanCreationOrder(source.CreationOrder), Name = ClanDataRules.RequireClanName(source.Name), Description = ClanDataRules.RequireClanDescription(source.Description), EmblemKey = ClanDataRules.RequireClanEmblemKey(source.EmblemKey) }; if (source.Members == null) { throw new InvalidDataException("Clan '" + clanState.Name + "' is missing the members sequence."); } ClanDataRules.RequireCount(source.Members.Count, 512, "clan member"); if (source.Members.Count == 0) { throw new InvalidDataException("Clan '" + clanState.Name + "' has no members."); } int num = 0; foreach (YamlMemberDto member in source.Members) { YamlMemberDto obj = member ?? throw new InvalidDataException("Clan '" + clanState.Name + "' contains a null member entry."); ClanPlayerRef player = ReadPlayer(obj.Player, "clan member"); ClanRole clanRole = ParseRole(obj.Role, "clan role"); if (clanRole == ClanRole.Leader) { num++; } if (clanState.Members.ContainsKey(player.Id)) { throw new InvalidDataException("Duplicate member '" + player.Id + "' in clan '" + clanState.Name + "'."); } clanState.Members.Add(player.Id, new ClanMember { Player = player, Role = clanRole }); } if (num != 1) { throw new InvalidDataException($"Clan '{clanState.Name}' must have exactly one leader; found {num}."); } if (source.Applications == null) { throw new InvalidDataException("Clan '" + clanState.Name + "' is missing the applications sequence."); } ClanDataRules.RequireCount(source.Applications.Count, 512, "application"); foreach (YamlApplicationDto application in source.Applications) { ClanPlayerRef value = ReadPlayer((application ?? throw new InvalidDataException("Clan '" + clanState.Name + "' contains a null application entry.")).Player, "application player"); if (clanState.Applications.ContainsKey(value.Id)) { throw new InvalidDataException("Duplicate application from '" + value.Id + "' in clan '" + clanState.Name + "'."); } clanState.Applications.Add(value.Id, value); } return clanState; } private static ClanInvite ReadInvite(YamlInviteDto? source) { if (source == null) { throw new InvalidDataException("Clan save contains a null invite entry."); } return new ClanInvite { InviteId = ClanDataRules.RequireText(source.InviteId, 64, "invite id", allowEmpty: false), ClanId = ClanDataRules.RequireClanId(source.ClanId, "invite clan id"), FromName = ClanDataRules.RequireText(source.FromName, 64, "invite sender name"), Target = ReadPlayer(source.Target, "invite target") }; } private static ClanPlayerRef ReadPlayer(YamlPlayerDto? source, string fieldName) { if (source == null) { throw new InvalidDataException(fieldName + " is missing."); } string platformId = ClanDataRules.RequirePlatformId(source.PlatformId, fieldName + " platform id"); long playerId = ClanDataRules.RequireCharacterPlayerId(source.CharacterPlayerId, fieldName + " character player id"); string name = ClanDataRules.RequireText(source.Name, 64, fieldName + " name"); ClanPlayerRef result = new ClanPlayerRef(platformId, playerId, name); if (!result.IsValid) { throw new InvalidDataException(fieldName + " identity is invalid."); } return result; } private static ClanRole ParseRole(string? value, string fieldName) { string text = ClanDataRules.RequireText(value, 16, fieldName, allowEmpty: false); return text switch { "leader" => ClanRole.Leader, "officer" => ClanRole.Officer, "member" => ClanRole.Member, "guest" => ClanRole.Guest, _ => throw new InvalidDataException(fieldName + " has unknown value '" + text + "'."), }; } private static void Save(bool wardAuthorizationChanged = false) { try { string text = ResolveSaveFile(); if (!string.Equals(_loadedSaveFile, text, StringComparison.Ordinal)) { throw new InvalidOperationException("The clan registry storage changed while clan state was being updated."); } ValidateClanIndexes(); ClanDataRules.RequireCount(ClansById.Count, 1024, "clan"); ClanDataRules.RequireCount(PendingInvitesByTarget.Count, 4096, "invite"); byte[] array = SerializeSave(CreateSaveDocument()); if (array.Length > 67108864) { throw new InvalidDataException($"Clan save exceeds the {67108864}-byte limit."); } ParseSave(array); WriteAtomically(text, array); _directoryInvalidationPending = true; } catch { RecoverPersistedStateAfterSaveFailure(); throw; } if (wardAuthorizationChanged) { ClanApi.NotifyRegistryChanged(); } } private static void RecoverPersistedStateAfterSaveFailure() { if (!RestorePersistedState()) { _loadedSaveFile = null; LastPositionUpdateByPlayer.Clear(); SwapState(new RegistryData()); ClanPlugin.ClanLogger.LogError((object)"Clan registry was invalidated after both saving and restoring the persisted state failed. The next registry access must reload clans.yml."); } } private static void PreserveRuntimeMemberState(RegistryData candidate) { foreach (KeyValuePair item in ClansById) { if (!candidate.ClansById.TryGetValue(item.Key, out ClanState value)) { continue; } foreach (KeyValuePair member in item.Value.Members) { if (value.Members.TryGetValue(member.Key, out ClanMember value2)) { value2.LastClanChatTime = member.Value.LastClanChatTime; value2.LastClanPingTime = member.Value.LastClanPingTime; } } } } private static void ValidateClanIndexes() { if (ClansById.Count != ClansByName.Count) { throw new InvalidDataException("Clan id and name indexes contain different counts."); } foreach (KeyValuePair item in ClansByName) { string y = ClanDataRules.RequireClanName(item.Key); if (!StringComparer.Ordinal.Equals(item.Key, y) || !StringComparer.Ordinal.Equals(item.Value.Name, item.Key) || !ClansById.TryGetValue(item.Value.ClanId, out ClanState value) || value != item.Value) { throw new InvalidDataException("Clan name index '" + item.Key + "' is not canonical or points to the wrong clan."); } } int num = 0; int num2 = 0; HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); foreach (KeyValuePair item2 in ClansById) { ClanState value2 = item2.Value; long num3 = RequireClanCreationOrder(value2.CreationOrder); if (!hashSet.Add(num3)) { throw new InvalidDataException($"Clan '{item2.Key}' has duplicate creation order '{num3}'."); } if (!StringComparer.Ordinal.Equals(item2.Key, value2.ClanId) || !ClansByName.TryGetValue(value2.Name, out ClanState value3) || value3 != value2) { throw new InvalidDataException("Clan '" + item2.Key + "' is inconsistent with the registry indexes."); } foreach (KeyValuePair member in value2.Members) { ClanMember value4 = member.Value; if (value4 == null || !value4.Player.IsValid || !StringComparer.Ordinal.Equals(member.Key, value4.Player.Id)) { throw new InvalidDataException("Clan '" + item2.Key + "' has an inconsistent member index for '" + member.Key + "'."); } if (ClanDataRules.RequireEnum(value4.Role, "clan role") == ClanRole.Guest) { if (!hashSet3.Add(member.Key) || !GuestClanByPlayerId.TryGetValue(member.Key, out ClanState value5) || value5 != value2) { throw new InvalidDataException("Clan '" + item2.Key + "' has an inconsistent Guest index for '" + member.Key + "'."); } num2++; } else { if (!hashSet2.Add(member.Key) || !PrimaryClanByPlayerId.TryGetValue(member.Key, out ClanState value6) || value6 != value2) { throw new InvalidDataException("Clan '" + item2.Key + "' has an inconsistent primary index for '" + member.Key + "'."); } num++; } } } if (PrimaryClanByPlayerId.Count != num || GuestClanByPlayerId.Count != num2) { throw new InvalidDataException("The role-aware player-to-clan indexes contain a different number of members than the clans."); } } private static bool RestorePersistedState() { string loadedSaveFile = _loadedSaveFile; if (loadedSaveFile == null || string.IsNullOrWhiteSpace(loadedSaveFile)) { return false; } try { SwapState(Load(loadedSaveFile)); LastPositionUpdateByPlayer.Clear(); return true; } catch (Exception arg) { ClanPlugin.ClanLogger.LogError((object)$"Failed to restore clan state after a save error: {arg}"); return false; } } private static YamlRegistryDocument CreateSaveDocument() { List list = new List(); List list2 = new List(); YamlRegistryDocument result = new YamlRegistryDocument { FormatVersion = 6, Clans = list, PendingInvites = list2 }; foreach (ClanState item in ClansById.Values.OrderBy((ClanState clan) => clan.ClanId, StringComparer.Ordinal)) { list.Add(CreateClanDto(item)); } foreach (KeyValuePair item2 in PendingInvitesByTarget.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { ClanInvite value = item2.Value; if (value == null || !value.Target.IsValid || !StringComparer.Ordinal.Equals(item2.Key, value.Target.Id)) { throw new InvalidDataException("Pending invite target index '" + item2.Key + "' is inconsistent."); } list2.Add(CreateInviteDto(value)); } return result; } private static byte[] SerializeSave(YamlRegistryDocument document) { string text = YamlSerializer.Serialize(document).Replace("\r\n", "\n").Replace('\r', '\n'); if (!text.EndsWith("\n", StringComparison.Ordinal)) { text += "\n"; } return StrictUtf8.GetBytes(text); } private static YamlClanDto CreateClanDto(ClanState clan) { ClanDataRules.RequireCount(clan.Members.Count, 512, "clan member"); ClanDataRules.RequireCount(clan.Applications.Count, 512, "application"); if (clan.Members.Count == 0) { throw new InvalidDataException("Clan '" + clan.Name + "' has no members."); } List list = new List(); List list2 = new List(); YamlClanDto result = new YamlClanDto { ClanId = ClanDataRules.RequireClanId(clan.ClanId), CreationOrder = RequireClanCreationOrder(clan.CreationOrder), Name = ClanDataRules.RequireClanName(clan.Name), Description = ClanDataRules.RequireClanDescription(clan.Description), EmblemKey = ClanDataRules.RequireClanEmblemKey(clan.EmblemKey), Members = list, Applications = list2 }; int num = 0; foreach (KeyValuePair item in clan.Members.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { ClanMember value = item.Value; if (value == null || !value.Player.IsValid || !StringComparer.Ordinal.Equals(item.Key, value.Player.Id)) { throw new InvalidDataException("Clan '" + clan.Name + "' contains an inconsistent member '" + item.Key + "'."); } ClanRole clanRole = ClanDataRules.RequireEnum(value.Role, "clan role"); if (clanRole == ClanRole.Leader) { num++; } list.Add(new YamlMemberDto { Player = CreatePlayerDto(value.Player, "clan member"), Role = FormatRole(clanRole) }); } if (num != 1) { throw new InvalidDataException("Clan '" + clan.Name + "' must have exactly one leader."); } foreach (KeyValuePair item2 in clan.Applications.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { ClanPlayerRef value2 = item2.Value; if (!value2.IsValid || !StringComparer.Ordinal.Equals(item2.Key, value2.Id)) { throw new InvalidDataException("Clan '" + clan.Name + "' contains an inconsistent application '" + item2.Key + "'."); } list2.Add(new YamlApplicationDto { Player = CreatePlayerDto(value2, "application player") }); } return result; } private static YamlInviteDto CreateInviteDto(ClanInvite invite) { return new YamlInviteDto { InviteId = ClanDataRules.RequireText(invite.InviteId, 64, "invite id", allowEmpty: false), ClanId = ClanDataRules.RequireClanId(invite.ClanId, "invite clan id"), FromName = ClanDataRules.RequireText(invite.FromName, 64, "invite sender name"), Target = CreatePlayerDto(invite.Target, "invite target") }; } private static YamlPlayerDto CreatePlayerDto(ClanPlayerRef player, string fieldName) { if (!player.IsValid) { throw new InvalidDataException(fieldName + " identity is invalid."); } return new YamlPlayerDto { PlatformId = ClanDataRules.RequirePlatformId(player.PlatformId, fieldName + " platform id"), CharacterPlayerId = ClanDataRules.RequireCharacterPlayerId(player.CharacterPlayerId, fieldName + " character player id"), Name = ClanDataRules.RequireText(player.Name, 64, fieldName + " name") }; } private static string FormatRole(ClanRole role) { return ClanDataRules.RequireEnum(role, "clan role") switch { ClanRole.Leader => "leader", ClanRole.Officer => "officer", ClanRole.Member => "member", ClanRole.Guest => "guest", _ => throw new InvalidDataException("Clan role is unsupported."), }; } private static void WriteAtomically(string saveFile, byte[] bytes) { string? directoryName = Path.GetDirectoryName(saveFile); if (string.IsNullOrWhiteSpace(directoryName)) { throw new InvalidOperationException("Clan save directory is invalid."); } Directory.CreateDirectory(directoryName); string text = saveFile + ".tmp-" + Guid.NewGuid().ToString("N"); try { using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(saveFile)) { string destinationBackupFileName = saveFile + ".bak"; File.Replace(text, saveFile, destinationBackupFileName, ignoreMetadataErrors: true); } else { File.Move(text, saveFile); } } catch { try { if (File.Exists(text)) { File.Delete(text); } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Failed to clean temporary clan save '" + text + "': " + ex.Message)); } throw; } } private static byte[] ReadSaveBytes(string saveFile) { FileInfo fileInfo = new FileInfo(saveFile); if (fileInfo.Length < 0 || fileInfo.Length > 67108864) { throw new InvalidDataException($"Clan save exceeds the {67108864}-byte limit."); } byte[] array = File.ReadAllBytes(saveFile); if (array.Length > 67108864) { throw new InvalidDataException($"Clan save exceeds the {67108864}-byte limit."); } return array; } private static string QuarantineUnsupportedSave(string saveFile) { string text = DateTime.UtcNow.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture); for (int i = 0; i < 10; i++) { string text2 = saveFile + ".unsupported-or-corrupt-" + text + "-" + Guid.NewGuid().ToString("N"); if (!File.Exists(text2)) { try { File.Move(saveFile, text2); return text2; } catch (IOException) when (File.Exists(saveFile) && File.Exists(text2)) { } } } throw new IOException("Could not allocate a unique quarantine file for '" + saveFile + "'."); } private static void SwapState(RegistryData data) { ClansById = data.ClansById; ClansByName = data.ClansByName; PrimaryClanByPlayerId = data.PrimaryClanByPlayerId; GuestClanByPlayerId = data.GuestClanByPlayerId; PendingInvitesByTarget = data.PendingInvitesByTarget; } } internal static class ClanRpc { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClanRpc { private static void Postfix(ZNet __instance, ZNetPeer peer) { ClanEmoji.RegisterEmojiFileRpc(__instance, peer); if (__instance.IsServer()) { peer.m_rpc.Register(RequestRpc, (Action)delegate(ZRpc rpc, ZPackage package) { ServerHandleRequest(peer, rpc, package); }); } else { peer.m_rpc.Register(ResponseRpc, (Action)ClientHandleResponse); } } } [HarmonyPatch(typeof(Game), "Start")] private static class ResetClanSessionOnGameStart { private static void Postfix() { ResetSession(); } } [HarmonyPatch(typeof(ZNet), "Shutdown")] private static class ClearClanSession { private static void Prefix() { ResetSession(); } } [HarmonyPatch(typeof(ZNet), "Disconnect", new Type[] { typeof(ZNetPeer) })] private static class ClearEmojiPeerOnDisconnect { private static void Prefix(ZNetPeer peer, out ClanPlayerRef __state) { if (peer?.m_rpc != null && PeerIdentities.TryGetValue(peer.m_rpc, out var value)) { __state = value; } else { ZNet instance = ZNet.instance; __state = ((instance != null && instance.IsServer()) ? ClanIdentity.FromPeer(peer) : default(ClanPlayerRef)); } ClanEmoji.ForgetEmojiPeer(peer?.m_rpc); if (peer?.m_rpc != null) { DirectoryRequestBudgets.Remove(peer.m_rpc); SnapshotRequestBudgets.Remove(peer.m_rpc); HudRequestBudgets.Remove(peer.m_rpc); PeerIdentities.Remove(peer.m_rpc); } } private static void Postfix(ClanPlayerRef __state) { ClanRegistry.NotifyPlayerDisconnected(__state); } } private sealed class RequestBudget { public float WindowStartedAt; public int Requests; public RequestBudget(float windowStartedAt) { WindowStartedAt = windowStartedAt; } } internal static readonly string IdentityNotReadyStatus = ClanLocalization.EncodeStatus("clan_status_identity_not_ready"); internal static readonly string IdentityRejectedStatus = ClanLocalization.EncodeStatus("clan_status_identity_rejected"); internal static readonly string SnapshotRateLimitedStatus = ClanLocalization.EncodeStatus("clan_status_snapshot_rate_limited"); internal static readonly string MutationRateLimitedStatus = ClanLocalization.EncodeStatus("clan_status_mutation_rate_limited"); internal static readonly string DirectoryRateLimitedStatus = ClanLocalization.EncodeStatus("clan_status_directory_rate_limited"); internal static readonly string StateUnavailableStatus = ClanLocalization.EncodeStatus("clan_status_state_unavailable"); private const int MaximumRequestBytes = 16384; private const int MaximumDirectoryResponseBytes = 524288; private const int MaximumDirectoryRequestsPerWindow = 1; private const float DirectoryRequestWindowSeconds = 2f; private const int MaximumSnapshotRequestsPerWindow = 5; private const float SnapshotRequestWindowSeconds = 2f; private const int MaximumHudRequestsPerWindow = 5; private const float HudRequestWindowSeconds = 2f; private const int MaximumMutationRequestsPerWindow = 6; private const float MutationRequestWindowSeconds = 5f; private const int MutationBudgetPruneThreshold = 256; private const int InitialSnapshotRetryCount = 20; private const float InitialSnapshotRetryIntervalSeconds = 0.5f; private const string ProtocolVersion = "v13"; private static readonly string DirectoryTruncatedStatus = ClanLocalization.EncodeStatus("clan_status_directory_truncated"); private static readonly string RequestRpc = "sighsorry.Clan.rpc.request.v13"; private static readonly string ResponseRpc = "sighsorry.Clan.rpc.response.v13"; private static readonly Dictionary DirectoryRequestBudgets = new Dictionary(); private static readonly Dictionary SnapshotRequestBudgets = new Dictionary(); private static readonly Dictionary HudRequestBudgets = new Dictionary(); private static readonly Dictionary MutationRequestBudgets = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary PeerIdentities = new Dictionary(); private static readonly object RequestIdLock = new object(); private static long _nextRequestId; private static long _pendingDirectoryRequestId; private static int _initialSnapshotRetriesRemaining; private static float _nextInitialSnapshotRetryAt; private static bool _initialSnapshotBootstrapStarted; private static bool _initialSnapshotBootstrapComplete; private static bool _identityReady; private static bool _retryDirectoryWhenIdentityReady; private static float _directoryIdentityRetryAt; private static bool _hudRecoveryRequestInProgress; public static ClanClientSnapshot CurrentSnapshot { get; private set; } = new ClanClientSnapshot(); public static ClanHudSnapshot CurrentHudSnapshot { get; private set; } = new ClanHudSnapshot(); public static ClanDirectorySnapshot CurrentDirectory { get; private set; } = new ClanDirectorySnapshot(); public static bool IsDirectoryRequestPending => _pendingDirectoryRequestId > 0; internal static bool IsIdentityReady => _identityReady; public static event Action? SnapshotChanged; public static event Action? HudSnapshotChanged; public static event Action? DirectoryChanged; public static event Action? StatusReceived; public static event Action? ChatReceived; public static bool Send(ClanRequest request) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); try { request.Write(val); } catch (InvalidDataException ex) { ClanPlugin.ClanLogger.LogWarning((object)("Rejected invalid local clan request: " + ex.Message)); NotifyStatus(ClanLocalization.Text("clan_status_invalid_request_server")); return false; } ZNet instance = ZNet.instance; ZRpc val2 = ((instance != null) ? instance.GetServerRPC() : null); if (val2 != null) { val2.Invoke(RequestRpc, new object[1] { val }); return true; } ZNet instance2 = ZNet.instance; if (instance2 != null && instance2.IsServer()) { val.SetPos(0); ClanRegistry.HandleRequest(null, ClanRequest.Read(val)); return true; } NotifyStatus(ClanLocalization.Text("clan_status_server_not_connected")); return false; } public static void RequestSnapshot() { Send(ClanRequest.Simple(ClanRequestType.RequestSnapshot)); } public static void RequestHudSnapshot(string clanId) { if (!string.IsNullOrWhiteSpace(clanId)) { long hudSelectionRevision = 0L; long hudStateRevision = 0L; if (StringComparer.Ordinal.Equals(CurrentHudSnapshot.ClanId, clanId)) { hudSelectionRevision = CurrentHudSnapshot.SelectionRevision; hudStateRevision = CurrentHudSnapshot.StateRevision; } Send(new ClanRequest { Type = ClanRequestType.RequestHud, ClanId = clanId, HudSelectionRevision = hudSelectionRevision, HudStateRevision = hudStateRevision }); } } public static void Tick() { float realtimeSinceStartup = Time.realtimeSinceStartup; EnsureInitialSnapshotBootstrap(); if (!_initialSnapshotBootstrapComplete && _initialSnapshotRetriesRemaining > 0 && realtimeSinceStartup >= _nextInitialSnapshotRetryAt && CanRunInitialSnapshotBootstrap()) { _initialSnapshotRetriesRemaining--; _nextInitialSnapshotRetryAt = realtimeSinceStartup + 0.5f; RequestSnapshot(); } if (_identityReady && _retryDirectoryWhenIdentityReady && realtimeSinceStartup >= _directoryIdentityRetryAt) { _retryDirectoryWhenIdentityReady = false; RequestDirectory(); } } private static void EnsureInitialSnapshotBootstrap() { if (!_initialSnapshotBootstrapStarted && !_initialSnapshotBootstrapComplete && CanRunInitialSnapshotBootstrap()) { _initialSnapshotBootstrapStarted = true; _identityReady = false; _initialSnapshotRetriesRemaining = 20; _nextInitialSnapshotRetryAt = Time.realtimeSinceStartup; if (CanRunInitialSnapshotBootstrap()) { _nextInitialSnapshotRetryAt += 0.5f; RequestSnapshot(); } } } private static bool CanRunInitialSnapshotBootstrap() { ZNet instance = ZNet.instance; if ((Object)(object)Game.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)instance != (Object)null) { if (instance.GetServerRPC() == null) { return instance.IsServer(); } return true; } return false; } public static long RequestDirectory() { _retryDirectoryWhenIdentityReady = false; ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || (instance.GetServerRPC() == null && !instance.IsServer())) { _pendingDirectoryRequestId = 0L; NotifyStatus(ClanLocalization.Text("clan_status_server_not_connected")); return 0L; } long num = (_pendingDirectoryRequestId = NextRequestId()); if (!Send(new ClanRequest { Type = ClanRequestType.RequestDirectory, RequestId = num })) { _pendingDirectoryRequestId = 0L; return 0L; } return num; } internal static long NextRequestId() { lock (RequestIdLock) { if (_nextRequestId == long.MaxValue) { _nextRequestId = 0L; } return ++_nextRequestId; } } public static void InvalidateDirectory() { _pendingDirectoryRequestId = 0L; _retryDirectoryWhenIdentityReady = true; _directoryIdentityRetryAt = Time.realtimeSinceStartup + 2f + 0.1f; CurrentDirectory = new ClanDirectorySnapshot(); Publish(ClanRpc.DirectoryChanged, CurrentDirectory, "directory"); } public static void NotifyStatus(string message) { Publish(ClanRpc.StatusReceived, ClanLocalization.ResolveStatus(message), "status"); } public static bool TryPinPeerIdentity(ZNetPeer? peer, ClanPlayerRef player) { if (peer?.m_rpc == null) { return true; } if (!player.IsValid) { return false; } if (PeerIdentities.TryGetValue(peer.m_rpc, out var value)) { return value == player; } PeerIdentities.Add(peer.m_rpc, player); return true; } public static bool TryGetPinnedPeerIdentity(ZNetPeer? peer, out ClanPlayerRef player) { player = default(ClanPlayerRef); if (peer?.m_rpc != null) { return PeerIdentities.TryGetValue(peer.m_rpc, out player); } return false; } public static void SendSnapshot(ZNetPeer? peer, ClanClientSnapshot snapshot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(0); snapshot.Write(val); SendResponse(peer, val); } public static void SendHudSnapshot(ZNetPeer? peer, ClanHudSnapshot snapshot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(6); snapshot.Write(val); SendResponse(peer, val); } public static void SendDirectorySnapshot(ZNetPeer? peer, ClanDirectorySnapshot snapshot) { int count = snapshot.PublicClans.Count; int count2 = snapshot.Players.Count; ZPackage val = CreateDirectoryResponse(snapshot, count, count2); if (val.Size() <= 524288) { SendResponse(peer, val); return; } snapshot.IsTruncated = true; if (string.IsNullOrWhiteSpace(snapshot.Status)) { snapshot.Status = DirectoryTruncatedStatus; } count2 = FindLargestPlayerPrefix(snapshot, count, count2); val = CreateDirectoryResponse(snapshot, count, count2); if (val.Size() > 524288) { count2 = 0; count = FindLargestClanPrefix(snapshot, count); val = CreateDirectoryResponse(snapshot, count, count2); } if (val.Size() > 524288) { snapshot.Status = ClanLocalization.EncodeStatus("clan_status_directory_response_too_large"); snapshot.IsTruncated = true; val = CreateDirectoryResponse(snapshot, 0, 0); } SendResponse(peer, val); } private static int FindLargestPlayerPrefix(ClanDirectorySnapshot snapshot, int clanCount, int maximumPlayerCount) { int num = 0; int num2 = maximumPlayerCount; int result = 0; while (num <= num2) { int num3 = num + (num2 - num) / 2; if (CreateDirectoryResponse(snapshot, clanCount, num3).Size() <= 524288) { result = num3; num = num3 + 1; } else { num2 = num3 - 1; } } return result; } private static int FindLargestClanPrefix(ClanDirectorySnapshot snapshot, int maximumClanCount) { int num = 0; int num2 = maximumClanCount; int result = 0; while (num <= num2) { int num3 = num + (num2 - num) / 2; if (CreateDirectoryResponse(snapshot, num3, 0).Size() <= 524288) { result = num3; num = num3 + 1; } else { num2 = num3 - 1; } } return result; } private static ZPackage CreateDirectoryResponse(ClanDirectorySnapshot snapshot, int clanCount, int playerCount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(4); snapshot.Write(val, clanCount, playerCount); return val; } public static void BroadcastChat(ClanState clan, string senderName, string message) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown Dictionary peers = BuildPeerLookup(); ZPackage val = new ZPackage(); val.Write(1); ClanDataRules.WriteClanId(val, clan.ClanId); ClanDataRules.WritePlayerName(val, senderName, "chat sender"); ClanDataRules.WriteText(val, message, 400, "chat message", allowEmpty: false); foreach (ClanMember value in clan.Members.Values) { if (ClanRegistry.IsEffectiveMember(clan, value)) { SendResponse(FindPeer(value.Player, peers), val, value.Player); } } } public static void BroadcastPing(ClanState clan, ClanPlayerRef sender, Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) Dictionary peers = BuildPeerLookup(); ZPackage val = new ZPackage(); val.Write(2); ClanDataRules.WriteClanId(val, clan.ClanId); sender.Write(val); val.Write(position); foreach (ClanMember value in clan.Members.Values) { if (ClanRegistry.IsEffectiveMember(clan, value)) { SendResponse(FindPeer(value.Player, peers), val, value.Player); } } } public static void BroadcastPosition(ClanState clan, ClanPlayerRef sender, Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) Dictionary peers = BuildPeerLookup(); ZPackage val = new ZPackage(); val.Write(3); ClanDataRules.WriteClanId(val, clan.ClanId); sender.Write(val); val.Write(position); foreach (ClanMember value in clan.Members.Values) { if (!(value.Player == sender) && ClanRegistry.IsEffectiveMember(clan, value)) { SendResponse(FindPeer(value.Player, peers), val, value.Player); } } } public static void BroadcastDirectoryInvalidation() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(5); if ((Object)(object)ZNet.instance != (Object)null) { foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { SendResponse(connectedPeer, val); } } if ((Object)(object)Player.m_localPlayer != (Object)null) { TryHandleResponsePackage(val); } } private static void SendResponse(ZNetPeer? peer, ZPackage package, ClanPlayerRef target = default(ClanPlayerRef)) { try { if (peer != null) { peer.m_rpc.Invoke(ResponseRpc, new object[1] { package }); } else if (!target.IsValid || ((Object)(object)Player.m_localPlayer != (Object)null && target == ClanPlayerRef.Local())) { TryHandleResponsePackage(package); } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)$"Failed to send clan response to {target}: {ex.Message}"); } } private static ZNetPeer? FindPeer(ClanPlayerRef player, IReadOnlyDictionary peers) { if (!player.IsValid || !peers.TryGetValue(player.Id, out ZNetPeer value)) { return null; } return value; } private static Dictionary BuildPeerLookup() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if ((Object)(object)ZNet.instance == (Object)null) { return dictionary; } foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { ClanPlayerRef player; ClanPlayerRef clanPlayerRef = (TryGetPinnedPeerIdentity(connectedPeer, out player) ? player : ClanIdentity.FromPeer(connectedPeer)); if (clanPlayerRef.IsValid) { dictionary[clanPlayerRef.Id] = connectedPeer; } } return dictionary; } private static void ClientHandleResponse(ZRpc rpc, ZPackage package) { TryHandleResponsePackage(package); } private static void TryHandleResponsePackage(ZPackage package) { //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) ClanResponseType clanResponseType = ClanResponseType.Snapshot; bool flag = false; try { package.SetPos(0); clanResponseType = (ClanResponseType)package.ReadInt(); switch (clanResponseType) { case ClanResponseType.Snapshot: { ClanClientSnapshot clanClientSnapshot = ClanClientSnapshot.Read(package); RequirePackageConsumed(package); flag = true; clanClientSnapshot.Status = ClanLocalization.ResolveStatus(clanClientSnapshot.Status); ClanOperationResultCode resultCode = clanClientSnapshot.ResponseResultCode; if ((resultCode == ClanOperationResultCode.IdentityUnavailable || (uint)(resultCode - 8) <= 1u || resultCode == ClanOperationResultCode.SnapshotRateLimited) ? true : false) { if (clanClientSnapshot.ResponseRequestId > 0) { CopySnapshotPresentation(CurrentSnapshot, clanClientSnapshot); Publish(ClanRpc.SnapshotChanged, clanClientSnapshot, "snapshot"); } if (!_initialSnapshotBootstrapComplete && _initialSnapshotRetriesRemaining > 0 && clanClientSnapshot.ResponseResultCode == ClanOperationResultCode.Unavailable) { _nextInitialSnapshotRetryAt = Time.realtimeSinceStartup + 0.5f; } else if (!_initialSnapshotBootstrapComplete && _initialSnapshotRetriesRemaining > 0 && clanClientSnapshot.ResponseResultCode == ClanOperationResultCode.SnapshotRateLimited) { _nextInitialSnapshotRetryAt = Time.realtimeSinceStartup + 2f + 0.1f; } Publish(ClanRpc.StatusReceived, clanClientSnapshot.Status, "status"); break; } if (_initialSnapshotBootstrapComplete || clanClientSnapshot.ResponseResultCode != ClanOperationResultCode.Failed) { _initialSnapshotRetriesRemaining = 0; _initialSnapshotBootstrapStarted = true; _initialSnapshotBootstrapComplete = true; } bool num = clanClientSnapshot.ResponseResultCode == ClanOperationResultCode.IdentityRejected; _identityReady = !num; if (num) { _retryDirectoryWhenIdentityReady = false; } bool num2 = !StringComparer.Ordinal.Equals(CurrentSnapshot.ClanId, clanClientSnapshot.ClanId); CurrentSnapshot = clanClientSnapshot; if (num2) { CurrentHudSnapshot = new ClanHudSnapshot(); Publish(ClanRpc.HudSnapshotChanged, CurrentHudSnapshot, "HUD snapshot"); } ClanMap.OnSnapshotChanged(CurrentSnapshot); Publish(ClanRpc.SnapshotChanged, CurrentSnapshot, "snapshot"); if (!string.IsNullOrWhiteSpace(CurrentSnapshot.Status)) { Publish(ClanRpc.StatusReceived, CurrentSnapshot.Status, "status"); } break; } case ClanResponseType.Chat: { string x = ClanDataRules.ReadClanId(package, "chat clan id"); string first = ClanDataRules.ReadText(package, 64, "chat sender"); string second = ClanDataRules.ReadText(package, 400, "chat message", allowEmpty: false); RequirePackageConsumed(package); flag = true; if (StringComparer.Ordinal.Equals(x, CurrentSnapshot.ClanId)) { Publish(ClanRpc.ChatReceived, first, second, "chat"); } break; } case ClanResponseType.MapPing: { string x3 = ClanDataRules.ReadClanId(package, "ping clan id"); ClanPlayerRef sender = ClanPlayerRef.Read(package); Vector3 position2 = ClanDataRules.ReadFiniteVector(package, "map ping position"); RequirePackageConsumed(package); flag = true; if (StringComparer.Ordinal.Equals(x3, CurrentSnapshot.ClanId)) { ClanMap.OnMapPing(sender, position2); } break; } case ClanResponseType.PositionUpdate: { string x2 = ClanDataRules.ReadClanId(package, "position clan id"); ClanPlayerRef player = ClanPlayerRef.Read(package); Vector3 position = ClanDataRules.ReadFiniteVector(package, "player position"); RequirePackageConsumed(package); flag = true; if (StringComparer.Ordinal.Equals(x2, CurrentSnapshot.ClanId)) { ClanMap.OnPositionUpdate(player, position); } break; } case ClanResponseType.Hud: { ClanHudSnapshot incoming = ClanHudSnapshot.Read(package); RequirePackageConsumed(package); flag = true; HandleHudSnapshot(incoming); break; } case ClanResponseType.Directory: { ClanDirectorySnapshot clanDirectorySnapshot = ClanDirectorySnapshot.Read(package); RequirePackageConsumed(package); flag = true; clanDirectorySnapshot.Status = ClanLocalization.ResolveStatus(clanDirectorySnapshot.Status); if (_pendingDirectoryRequestId <= 0 || clanDirectorySnapshot.RequestId != _pendingDirectoryRequestId) { break; } _pendingDirectoryRequestId = 0L; if (clanDirectorySnapshot.ResultCode == ClanOperationResultCode.IdentityUnavailable) { _retryDirectoryWhenIdentityReady = true; _directoryIdentityRetryAt = Time.realtimeSinceStartup + 2f + 0.1f; Publish(ClanRpc.StatusReceived, clanDirectorySnapshot.Status, "status"); break; } if (clanDirectorySnapshot.ResultCode == ClanOperationResultCode.RateLimited) { _retryDirectoryWhenIdentityReady = true; _directoryIdentityRetryAt = Time.realtimeSinceStartup + 2f + 0.1f; } if (!clanDirectorySnapshot.IsTruncated) { ClanOperationResultCode resultCode = clanDirectorySnapshot.ResultCode; if (resultCode != ClanOperationResultCode.None && resultCode != ClanOperationResultCode.Success && clanDirectorySnapshot.PublicClans.Count == 0 && clanDirectorySnapshot.Players.Count == 0) { clanDirectorySnapshot.PublicClans.AddRange(CurrentDirectory.PublicClans); clanDirectorySnapshot.Players.AddRange(CurrentDirectory.Players); clanDirectorySnapshot.IsTruncated = CurrentDirectory.IsTruncated; } } CurrentDirectory = clanDirectorySnapshot; Publish(ClanRpc.DirectoryChanged, CurrentDirectory, "directory"); if (!string.IsNullOrWhiteSpace(CurrentDirectory.Status)) { Publish(ClanRpc.StatusReceived, CurrentDirectory.Status, "status"); } break; } case ClanResponseType.DirectoryInvalidated: RequirePackageConsumed(package); flag = true; _pendingDirectoryRequestId = 0L; _retryDirectoryWhenIdentityReady = true; _directoryIdentityRetryAt = Time.realtimeSinceStartup + 0.1f; CurrentDirectory = new ClanDirectorySnapshot(); Publish(ClanRpc.DirectoryChanged, CurrentDirectory, "directory"); break; default: throw new InvalidOperationException($"Unknown clan response type {(int)clanResponseType}."); } } catch (Exception ex) when (!flag) { ClanPlugin.ClanLogger.LogWarning((object)("Rejected malformed clan response: " + ex.Message)); } catch (Exception arg) { ClanPlugin.ClanLogger.LogError((object)$"Failed to apply decoded clan {clanResponseType} response: {arg}"); } } private static void CopySnapshotPresentation(ClanClientSnapshot source, ClanClientSnapshot target) { target.PrimaryClanId = source.PrimaryClanId; target.PrimaryClanName = source.PrimaryClanName; target.PrimaryRole = source.PrimaryRole; target.GuestClanId = source.GuestClanId; target.GuestClanName = source.GuestClanName; target.ClanId = source.ClanId; target.ClanName = source.ClanName; target.ClanDescription = source.ClanDescription; target.ClanEmblemKey = source.ClanEmblemKey; target.SelfRole = source.SelfRole; target.Roster.Clear(); target.Roster.AddRange(source.Roster); target.Applications.Clear(); target.Applications.AddRange(source.Applications); target.Invite = source.Invite; target.OwnApplicationClanId = source.OwnApplicationClanId; target.OwnApplicationClanName = source.OwnApplicationClanName; } private static void RequirePackageConsumed(ZPackage package) { if (package.GetPos() != package.Size()) { throw new InvalidOperationException("Clan response contains unexpected trailing data."); } } private static void HandleHudSnapshot(ClanHudSnapshot incoming) { if (!StringComparer.Ordinal.Equals(incoming.ClanId, CurrentSnapshot.ClanId)) { return; } if (incoming.ReplaceSelection) { CurrentHudSnapshot = incoming; Publish(ClanRpc.HudSnapshotChanged, CurrentHudSnapshot, "HUD snapshot"); } else if (!StringComparer.Ordinal.Equals(incoming.ClanId, CurrentHudSnapshot.ClanId) || incoming.SelectionRevision != CurrentHudSnapshot.SelectionRevision) { ResetHudSnapshotAndRequestFull(); } else { if (incoming.StateRevision == CurrentHudSnapshot.StateRevision) { return; } if (CurrentHudSnapshot.StateRevision == long.MaxValue || incoming.StateRevision != CurrentHudSnapshot.StateRevision + 1) { ResetHudSnapshotAndRequestFull(); return; } int[] array = new int[incoming.Players.Count]; for (int i = 0; i < incoming.Players.Count; i++) { string playerId = incoming.Players[i].PlayerId; int num = FindHudPlayerIndex(CurrentHudSnapshot, playerId); if (num < 0) { ResetHudSnapshotAndRequestFull(); return; } array[i] = num; } for (int j = 0; j < incoming.Players.Count; j++) { ClanHudPlayerSummary clanHudPlayerSummary = incoming.Players[j]; ClanHudPlayerSummary clanHudPlayerSummary2 = CurrentHudSnapshot.Players[array[j]]; clanHudPlayerSummary2.HasHealth = clanHudPlayerSummary.HasHealth; clanHudPlayerSummary2.CurrentHealth = clanHudPlayerSummary.CurrentHealth; clanHudPlayerSummary2.MaxHealth = clanHudPlayerSummary.MaxHealth; } CurrentHudSnapshot.StateRevision = incoming.StateRevision; CurrentHudSnapshot.ReplaceSelection = true; Publish(ClanRpc.HudSnapshotChanged, CurrentHudSnapshot, "HUD snapshot"); } } private static int FindHudPlayerIndex(ClanHudSnapshot snapshot, string playerId) { for (int i = 0; i < snapshot.Players.Count; i++) { if (StringComparer.Ordinal.Equals(snapshot.Players[i].PlayerId, playerId)) { return i; } } return -1; } private static void ResetHudSnapshotAndRequestFull() { CurrentHudSnapshot = new ClanHudSnapshot(); Publish(ClanRpc.HudSnapshotChanged, CurrentHudSnapshot, "HUD snapshot"); string clanId = CurrentSnapshot.ClanId; if (string.IsNullOrWhiteSpace(clanId) || _hudRecoveryRequestInProgress) { return; } try { _hudRecoveryRequestInProgress = true; RequestHudSnapshot(clanId); } finally { _hudRecoveryRequestInProgress = false; } } private static void ServerHandleRequest(ZNetPeer peer, ZRpc rpc, ZPackage package) { try { if (package.Size() > 16384) { ClanPlugin.ClanLogger.LogWarning((object)$"Rejected oversized clan request ({package.Size()} bytes) from peer {peer.m_uid}."); return; } package.SetPos(0); ClanRequest clanRequest = ClanRequest.Read(package); if (clanRequest.Type == ClanRequestType.RequestDirectory && !ConsumeRequest(rpc, DirectoryRequestBudgets, 1, 2f)) { SendDirectorySnapshot(peer, new ClanDirectorySnapshot { RequestId = clanRequest.RequestId, ResultCode = ClanOperationResultCode.RateLimited, Status = DirectoryRateLimitedStatus }); } else if (clanRequest.Type == ClanRequestType.RequestSnapshot && !ConsumeRequest(rpc, SnapshotRequestBudgets, 5, 2f)) { SendSnapshot(peer, new ClanClientSnapshot { Status = SnapshotRateLimitedStatus, ResponseResultCode = ClanOperationResultCode.SnapshotRateLimited }); } else if (clanRequest.Type != ClanRequestType.RequestHud || ConsumeRequest(rpc, HudRequestBudgets, 5, 2f)) { ClanRegistry.HandleRequest(peer, clanRequest); } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Rejected malformed clan request: " + ex.Message)); } } private static void ResetSession() { CurrentSnapshot = new ClanClientSnapshot(); CurrentHudSnapshot = new ClanHudSnapshot(); CurrentDirectory = new ClanDirectorySnapshot(); _pendingDirectoryRequestId = 0L; _initialSnapshotRetriesRemaining = 0; _nextInitialSnapshotRetryAt = 0f; _initialSnapshotBootstrapStarted = false; _initialSnapshotBootstrapComplete = false; _identityReady = false; _retryDirectoryWhenIdentityReady = false; _directoryIdentityRetryAt = 0f; _hudRecoveryRequestInProgress = false; DirectoryRequestBudgets.Clear(); SnapshotRequestBudgets.Clear(); HudRequestBudgets.Clear(); MutationRequestBudgets.Clear(); PeerIdentities.Clear(); ClanApi.ResetSession(); ClanRegistry.ResetOnlinePresence(); ClanMap.ResetSession(); ClanPanelController.ResetSearchState(); Publish(ClanRpc.SnapshotChanged, CurrentSnapshot, "snapshot"); Publish(ClanRpc.HudSnapshotChanged, CurrentHudSnapshot, "HUD snapshot"); Publish(ClanRpc.DirectoryChanged, CurrentDirectory, "directory"); } private static void Publish(Action? subscribers, T value, string eventName) { if (subscribers == null) { return; } Delegate[] invocationList = subscribers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(value); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan RPC {eventName} subscriber failed: {arg}"); } } } private static void Publish(Action? subscribers, TFirst first, TSecond second, string eventName) { if (subscribers == null) { return; } Delegate[] invocationList = subscribers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(first, second); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan RPC {eventName} subscriber failed: {arg}"); } } } internal static bool ConsumeMutationRequest(ClanPlayerRef actor) { if (!actor.IsValid) { return false; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (MutationRequestBudgets.Count >= 256 && !MutationRequestBudgets.ContainsKey(actor.Id)) { PruneExpiredMutationBudgets(realtimeSinceStartup); } if (!MutationRequestBudgets.TryGetValue(actor.Id, out RequestBudget value)) { value = new RequestBudget(realtimeSinceStartup); MutationRequestBudgets.Add(actor.Id, value); } return ConsumeBudget(value, 6, 5f, realtimeSinceStartup); } private static void PruneExpiredMutationBudgets(float now) { List list = null; foreach (KeyValuePair mutationRequestBudget in MutationRequestBudgets) { float num = now - mutationRequestBudget.Value.WindowStartedAt; if (num >= 5f || num < 0f) { if (list == null) { list = new List(); } list.Add(mutationRequestBudget.Key); } } if (list == null) { return; } foreach (string item in list) { MutationRequestBudgets.Remove(item); } } private static bool ConsumeRequest(ZRpc rpc, IDictionary budgets, int maximumRequests, float windowSeconds) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!budgets.TryGetValue(rpc, out RequestBudget value)) { value = new RequestBudget(realtimeSinceStartup); budgets.Add(rpc, value); } return ConsumeBudget(value, maximumRequests, windowSeconds, realtimeSinceStartup); } private static bool ConsumeBudget(RequestBudget budget, int maximumRequests, float windowSeconds, float? nowOverride = null) { float num = nowOverride ?? Time.realtimeSinceStartup; if (num - budget.WindowStartedAt >= windowSeconds || num < budget.WindowStartedAt) { budget.WindowStartedAt = num; budget.Requests = 0; } if (budget.Requests >= maximumRequests) { return false; } budget.Requests++; return true; } } internal static class ClanMap { [HarmonyPatch(typeof(Chat), "SendPing")] private static class SendClanPingPatch { private static bool Prefix(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return !TrySendClanPing(position); } } [HarmonyPatch(typeof(Chat), "RPC_ChatMessage")] private static class ClearClanPingPatch { private static void Prefix(Chat __instance, long sender) { Minimap instance = Minimap.instance; WorldTextInstance val = FindWorldText(__instance, sender); if (val == null || !ClanPingTexts.Remove(val) || (Object)(object)instance == (Object)null || !TryReadMinimapField>(instance, ref _tempShoutsField, "m_tempShouts", out var value) || !TryReadMinimapField>(instance, ref _pingPinsField, "m_pingPins", out var value2)) { return; } Sprite val2 = FindMinimapSprite(instance, (PinType)12); if ((Object)(object)val2 == (Object)null) { return; } for (int i = 0; i < value.Count && i < value2.Count; i++) { PinData pin = value2[i]; if (value[i] == val) { SetPinAppearance(pin, val2, doubleSize: false); } } } } [HarmonyPatch(typeof(Minimap), "UpdatePlayerPins")] private static class ClanMemberPinPatch { private static void Postfix(Minimap __instance) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) EnsureSprites(); if ((Object)(object)_clanPlayerIcon == (Object)null || !TryReadMinimapField(__instance, ref _tempPlayerInfoField, "m_tempPlayerInfo", out var value) || !TryReadMinimapField>(__instance, ref _playerPinsField, "m_playerPins", out var value2)) { return; } Sprite val = FindMinimapSprite(__instance, (PinType)10); if ((Object)(object)val == (Object)null) { return; } for (int i = 0; i < value.Count && i < value2.Count; i++) { PinData val2 = value2[i]; PlayerInfo val3 = value[i]; if (!(val2.m_name != val3.m_name)) { if (IsClanPlayer(val3)) { SetPinAppearance(val2, _clanPlayerIcon, doubleSize: true); } else if ((Object)(object)val2.m_icon == (Object)(object)_clanPlayerIcon) { SetPinAppearance(val2, val, doubleSize: false); } } } } } [HarmonyPatch(typeof(ZNet), "GetOtherPublicPlayers")] private static class AddClanPositionsToMinimapPatch { private static void Postfix(ZNet __instance, List playerList) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; if (!ClanPlugin.ShareClanPositions.Value.IsOn() || !ClanRpc.CurrentSnapshot.HasClan || (Object)(object)instance == (Object)null || !TryReadMinimapField(instance, ref _tempPlayerInfoField, "m_tempPlayerInfo", out var value) || playerList != value) { return; } for (int i = 0; i < playerList.Count; i++) { PlayerInfo val = playerList[i]; if (ClanIdentity.TryMatchRosterPlayer(val, ClanRpc.CurrentSnapshot.Roster, out var player) && ForcedPositions.TryGetValue(player.Id, out var value2)) { val.m_position = value2; playerList[i] = val; } } foreach (PlayerInfo player3 in __instance.GetPlayerList()) { if (!player3.m_publicPosition) { ZDOID characterID = player3.m_characterID; if (!((ZDOID)(ref characterID)).IsNone() && !(player3.m_characterID == __instance.LocalPlayerCharacterID) && ClanIdentity.TryMatchRosterPlayer(player3, ClanRpc.CurrentSnapshot.Roster, out var player2) && ForcedPositions.TryGetValue(player2.Id, out var value3)) { PlayerInfo item = player3; item.m_publicPosition = true; item.m_position = value3; playerList.Add(item); } } } } } [HarmonyPatch(typeof(Minimap), "UpdatePingPins")] private static class ClanPingPinPatch { private static void Postfix(Minimap __instance) { EnsureSprites(); if ((Object)(object)_clanPingIcon == (Object)null || !TryReadMinimapField>(__instance, ref _tempShoutsField, "m_tempShouts", out var value) || !TryReadMinimapField>(__instance, ref _pingPinsField, "m_pingPins", out var value2)) { return; } for (int i = 0; i < value.Count && i < value2.Count; i++) { PinData pin = value2[i]; WorldTextInstance key = value[i]; if (ClanPingTexts.TryGetValue(key, out object _)) { SetPinAppearance(pin, _clanPingIcon, doubleSize: true); } } } } private const float PositionSendInterval = 2f; private const float PositionMoveThreshold = 1f; private const float PositionHeartbeatInterval = 10f; private const string ClanPingHintName = "ClanPing"; private const string PingHintName = "PingPanel"; private static ConditionalWeakTable ClanPingTexts = new ConditionalWeakTable(); private static readonly Dictionary ForcedPositions = new Dictionary(StringComparer.Ordinal); private static readonly HashSet ClanPlayerIds = new HashSet(StringComparer.Ordinal); private static readonly List StalePositionIds = new List(); private static FieldRef>? _pingPinsField = CreateMinimapFieldAccessor>("m_pingPins"); private static FieldRef>? _playerPinsField = CreateMinimapFieldAccessor>("m_playerPins"); private static FieldRef>? _tempShoutsField = CreateMinimapFieldAccessor>("m_tempShouts"); private static FieldRef>? _tempPlayerInfoField = CreateMinimapFieldAccessor>("m_tempPlayerInfo"); private static Sprite? _clanPlayerIcon; private static Sprite? _clanPingIcon; private static Minimap? _clanPingHintOwner; private static GameObject? _clanPingHint; private static TMP_Text? _clanPingHintLabel; private static float _nextPositionSendTime; private static Vector3 _lastSentPosition = Vector3.positiveInfinity; private static float _lastPositionSentTime = float.NegativeInfinity; private static string _effectiveClanId = ""; private static bool _positionSharingActive; private static readonly Color ClanPingColor = new Color(1f, 0.78f, 0.25f, 1f); private static FieldRef? CreateMinimapFieldAccessor(string fieldName) where T : class { try { return AccessTools.FieldRefAccess(fieldName); } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Clan map integration for " + fieldName + " is unavailable: " + ex.Message)); return null; } } private static bool TryReadMinimapField(Minimap minimap, ref FieldRef? accessor, string fieldName, out T value) where T : class { value = null; FieldRef val = accessor; if (val == null) { return false; } try { value = val.Invoke(minimap); if (value != null) { return true; } accessor = null; ClanPlugin.ClanLogger.LogWarning((object)("Clan map integration for " + fieldName + " was disabled because the field was null.")); return false; } catch (Exception ex) { accessor = null; ClanPlugin.ClanLogger.LogWarning((object)("Clan map integration for " + fieldName + " was disabled: " + ex.Message)); return false; } } public static void ResetSession() { ForcedPositions.Clear(); ClanPlayerIds.Clear(); RestoreClanIcons(); DestroyClanPingHint(); DestroyGeneratedSprite(ref _clanPlayerIcon); DestroyGeneratedSprite(ref _clanPingIcon); ClanPingTexts = new ConditionalWeakTable(); _effectiveClanId = ""; _positionSharingActive = false; ResetPositionTimer(); } public static void OnSnapshotChanged(ClanClientSnapshot snapshot) { ClanPlayerIds.Clear(); foreach (ClanPlayerSummary item in snapshot.Roster) { if (item.IsOnline) { ClanPlayerIds.Add(item.Id); } } bool flag = !string.Equals(_effectiveClanId, snapshot.ClanId, StringComparison.Ordinal); if (!ClanPlugin.ShareClanPositions.Value.IsOn() || !snapshot.HasClan || flag) { ForcedPositions.Clear(); if (flag) { RestoreClanPlayerIcons(); } ResetPositionTimer(); } _effectiveClanId = (snapshot.HasClan ? snapshot.ClanId : ""); if (!ClanPlugin.ShareClanPositions.Value.IsOn() || !snapshot.HasClan) { RestoreClanPlayerIcons(); _positionSharingActive = false; return; } _positionSharingActive = true; StalePositionIds.Clear(); foreach (string key in ForcedPositions.Keys) { if (!ClanPlayerIds.Contains(key)) { StalePositionIds.Add(key); } } foreach (string stalePositionId in StalePositionIds) { ForcedPositions.Remove(stalePositionId); } StalePositionIds.Clear(); } public static void Tick() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) UpdateClanPingHint(); if (!ClanPlugin.ShareClanPositions.Value.IsOn() || !ClanRpc.CurrentSnapshot.HasClan || (Object)(object)Player.m_localPlayer == (Object)null) { if (ForcedPositions.Count != 0) { ForcedPositions.Clear(); } if (_positionSharingActive) { RestoreClanPlayerIcons(); } _positionSharingActive = false; ResetPositionTimer(); return; } _positionSharingActive = true; if (!(Time.time < _nextPositionSendTime)) { Vector3 position = ((Component)Player.m_localPlayer).transform.position; bool num = Vector3.Distance(position, _lastSentPosition) >= 1f; bool flag = Time.time < _lastPositionSentTime || Time.time - _lastPositionSentTime >= 10f; if (!num && !flag) { _nextPositionSendTime = Time.time + 2f; return; } _lastSentPosition = position; _lastPositionSentTime = Time.time; _nextPositionSendTime = Time.time + 2f; ClanRpc.Send(new ClanRequest { Type = ClanRequestType.UpdatePosition, ClanId = ClanRpc.CurrentSnapshot.ClanId, Position = position }); } } private static void UpdateClanPingHint() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_00ed: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_largeRoot == (Object)null) { return; } KeyboardShortcut value = ClanPlugin.ClanPingModifierKey.Value; KeyCode mainKey = ((KeyboardShortcut)(ref value)).MainKey; if (!ClanRpc.CurrentSnapshot.HasClan || (int)mainKey == 0 || PlatformPrefs.GetInt("KeyHints", 1) != 1 || !instance.m_largeRoot.activeInHierarchy) { if ((Object)(object)_clanPingHint != (Object)null && _clanPingHint.activeSelf) { _clanPingHint.SetActive(false); MarkClanPingHintLayoutForRebuild(); } return; } if ((Object)(object)_clanPingHintOwner != (Object)(object)instance || (Object)(object)_clanPingHint == (Object)null) { BuildClanPingHint(instance); } if ((Object)(object)_clanPingHint == (Object)null) { return; } if (!_clanPingHint.activeSelf) { _clanPingHint.SetActive(true); MarkClanPingHintLayoutForRebuild(); } if (!((Object)(object)_clanPingHintLabel == (Object)null)) { string text = ClanLocalization.Format("clan_map_ping_hint", FormatHintShortcut(value)); if (!string.Equals(_clanPingHintLabel.text, text, StringComparison.Ordinal)) { _clanPingHintLabel.text = text; MarkClanPingHintLayoutForRebuild(); } } } private static void BuildClanPingHint(Minimap minimap) { DestroyClanPingHint(); Transform val = minimap.m_largeRoot.transform.Find("KeyHints/keyboard_hints"); Transform val2 = ((val != null) ? val.Find("PingPanel") : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return; } Transform val3 = val.Find("ClanPing"); GameObject val4 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).gameObject : Object.Instantiate(((Component)val2).gameObject, val, false)); ((Object)val4).name = "ClanPing"; val4.transform.SetSiblingIndex(val2.GetSiblingIndex()); val4.SetActive(true); HorizontalLayoutGroup component = val4.GetComponent(); if ((Object)(object)component != (Object)null) { ((HorizontalOrVerticalLayoutGroup)component).spacing = -4f; } Transform obj = val4.transform.Find("Label"); TMP_Text val5 = ((obj != null) ? ((Component)obj).GetComponent() : null) ?? val4.GetComponentInChildren(true); if ((Object)(object)val5 == (Object)null) { if ((Object)(object)val3 == (Object)null) { Object.Destroy((Object)(object)val4); } } else { _clanPingHintOwner = minimap; _clanPingHint = val4; _clanPingHintLabel = val5; MarkClanPingHintLayoutForRebuild(); } } private static void MarkClanPingHintLayoutForRebuild() { GameObject? clanPingHint = _clanPingHint; Transform obj = ((clanPingHint != null) ? clanPingHint.transform.parent : null); RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null); if (val != null) { LayoutRebuilder.MarkLayoutForRebuild(val); } } private unsafe static string FormatHintShortcut(KeyboardShortcut shortcut) { return ((object)(*(KeyboardShortcut*)(&shortcut))/*cast due to .constrained prefix*/).ToString().Replace("LeftShift", "Shift").Replace("RightShift", "Shift") .Replace("LeftControl", "Ctrl") .Replace("RightControl", "Ctrl") .Replace("LeftAlt", "Alt") .Replace("RightAlt", "Alt"); } private static void DestroyClanPingHint() { if ((Object)(object)_clanPingHint != (Object)null) { Object.Destroy((Object)(object)_clanPingHint); } _clanPingHintOwner = null; _clanPingHint = null; _clanPingHintLabel = null; } public static void OnMapPing(ClanPlayerRef sender, Vector3 position) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) Chat instance = Chat.instance; if (!((Object)(object)instance == (Object)null) && sender.IsValid && IsClanPlayer(sender)) { EnsureSprites(); long num = FindTalkerId(sender); UserInfo val = new UserInfo { Name = (string.IsNullOrWhiteSpace(sender.Name) ? ClanLocalization.Text("clan_name_fallback") : sender.Name), UserId = new PlatformUserID(sender.PlatformId) }; instance.OnNewChatMessage((GameObject)null, num, position, (Type)3, val, ""); WorldTextInstance val2 = FindWorldText(instance, num); if (val2 != null) { ((Graphic)val2.m_textMeshField).color = ClanPingColor; ClanPingTexts.Remove(val2); ClanPingTexts.Add(val2, Array.Empty()); } } } private static WorldTextInstance? FindWorldText(Chat chat, long talkerId) { foreach (WorldTextInstance worldText in chat.WorldTexts) { if (worldText.m_talkerID == talkerId) { return worldText; } } return null; } public static void OnPositionUpdate(ClanPlayerRef player, Vector3 position) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (ClanPlugin.ShareClanPositions.Value.IsOn() && ClanRpc.CurrentSnapshot.HasClan && player.IsValid && !(player == ClanPlayerRef.Local()) && IsClanPlayer(player)) { ForcedPositions[player.Id] = position; } } private static bool TrySendClanPing(Vector3 position) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_0066: Unknown result type (might be due to invalid IL or missing references) if (!ClanRpc.CurrentSnapshot.HasClan || (Object)(object)Player.m_localPlayer == (Object)null || !ClanPlugin.ClanPingModifierKey.Value.IsKeyHeld()) { return false; } position.y = ((Component)Player.m_localPlayer).transform.position.y; ClanRpc.Send(new ClanRequest { Type = ClanRequestType.SendClanPing, ClanId = ClanRpc.CurrentSnapshot.ClanId, Position = position }); return true; } private static void ResetPositionTimer() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) _nextPositionSendTime = 0f; _lastSentPosition = Vector3.positiveInfinity; _lastPositionSentTime = float.NegativeInfinity; } private static bool IsClanPlayer(PlayerInfo player) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (ClanPlugin.ShareClanPositions.Value.IsOn() && ClanIdentity.TryMatchRosterPlayer(player, ClanRpc.CurrentSnapshot.Roster, out var player2)) { return ClanPlayerIds.Contains(player2.Id); } return false; } private static bool IsClanPlayer(ClanPlayerRef player) { if (player.IsValid) { return ClanPlayerIds.Contains(player.Id); } return false; } private static void RestoreClanIcons() { RestoreClanPlayerIcons(); Minimap instance = Minimap.instance; if ((Object)(object)_clanPingIcon == (Object)null || (Object)(object)instance == (Object)null || !TryReadMinimapField>(instance, ref _pingPinsField, "m_pingPins", out var value)) { return; } Sprite val = FindMinimapSprite(instance, (PinType)12); if ((Object)(object)val == (Object)null) { return; } foreach (PinData item in value) { if (!((Object)(object)item.m_icon != (Object)(object)_clanPingIcon)) { SetPinAppearance(item, val, doubleSize: false); } } } private static void RestoreClanPlayerIcons() { Minimap instance = Minimap.instance; if ((Object)(object)_clanPlayerIcon == (Object)null || (Object)(object)instance == (Object)null || !TryReadMinimapField>(instance, ref _playerPinsField, "m_playerPins", out var value)) { return; } Sprite val = FindMinimapSprite(instance, (PinType)10); if ((Object)(object)val == (Object)null) { return; } foreach (PinData item in value) { if (!((Object)(object)item.m_icon != (Object)(object)_clanPlayerIcon)) { SetPinAppearance(item, val, doubleSize: false); } } } private static void SetPinAppearance(PinData pin, Sprite? icon, bool doubleSize) { pin.m_icon = icon; pin.m_doubleSize = doubleSize; if ((Object)(object)pin.m_iconElement != (Object)null) { pin.m_iconElement.sprite = icon; } } private static Sprite? FindMinimapSprite(Minimap minimap, PinType type) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (minimap.m_icons == null) { return null; } foreach (SpriteData icon in minimap.m_icons) { if (icon.m_name == type) { return icon.m_icon; } } return null; } private static void DestroyGeneratedSprite(ref Sprite? sprite) { if (!((Object)(object)sprite == (Object)null)) { Texture2D texture = sprite.texture; Object.Destroy((Object)(object)sprite); if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } sprite = null; } } private static long FindTalkerId(ClanPlayerRef player) { //IL_001b: 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) //IL_0021: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null) { foreach (PlayerInfo onlinePlayer in ClanIdentity.GetOnlinePlayers()) { if (ClanIdentity.TryMatchRosterPlayer(onlinePlayer, ClanRpc.CurrentSnapshot.Roster, out var player2) && player2 == player) { ZDOID characterID = onlinePlayer.m_characterID; return ((ZDOID)(ref characterID)).UserID; } } } long num = 1469598103934665603L; string id = player.Id; foreach (char c in id) { num ^= c; num *= 1099511628211L; } return num switch { 0L => 1L, long.MinValue => long.MaxValue, _ => Math.Abs(num), }; } private static void EnsureSprites() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //IL_0030: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if ((!((Object)(object)_clanPlayerIcon != (Object)null) || !((Object)(object)_clanPingIcon != (Object)null)) && (int)SystemInfo.graphicsDeviceType != 4) { if (_clanPlayerIcon == null) { _clanPlayerIcon = CreateCircleSprite("Clan Player Icon", ClanUiFactory.GetClanColor(), Color.white); } if (_clanPingIcon == null) { _clanPingIcon = CreateDiamondSprite("Clan Ping Icon", ClanPingColor, Color.white); } } } private static Sprite CreateCircleSprite(string name, Color fill, Color accent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_00c6: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false) { name = name + " Texture" }; Color val2 = default(Color); ((Color)(ref val2))..ctor(0f, 0f, 0f, 0f); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(31.5f, 31.5f); for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num = Vector2.Distance(new Vector2((float)j, (float)i), val3); Color val4 = val2; if (num <= 27f) { val4 = ((num > 22f) ? accent : fill); } if (num <= 7f) { val4 = accent; } val.SetPixel(j, i, val4); } } val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f), 100f); } private static Sprite CreateDiamondSprite(string name, Color fill, Color accent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_009d: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false) { name = name + " Texture" }; Color val2 = default(Color); ((Color)(ref val2))..ctor(0f, 0f, 0f, 0f); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(31.5f, 31.5f); for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num = Math.Abs((float)j - val3.x) + Math.Abs((float)i - val3.y); Color val4 = val2; if (num <= 27f) { val4 = ((num > 22f) ? accent : fill); } if (num <= 7f) { val4 = accent; } val.SetPixel(j, i, val4); } } val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f), 100f); } } internal static class ClanHud { private enum HudPointerMode { None, Collapse, HeaderPending, Drag } private sealed class HudRowView { public GameObject Root; public RectTransform Rect; public Text Label; public RectTransform Bar; public RectTransform SlowFill; public RectTransform FastFill; public Text Health; public string PlayerId = ""; public float BarWidth = 100f; public float FastRatio; public float SlowRatio; public float SlowTargetRatio; public float SlowDelayUntil; } private const int MaxDisplayedMembers = 10; private const float RequestIntervalSeconds = 0.5f; private const float HeaderHeight = 42f; private const float HeaderToggleSize = 26f; private const float HeaderToggleGap = 7f; private const float HeaderRowGap = 5f; private const float RowHeight = 26f; private const float RowGap = 3f; private const float ColumnGap = 7f; private const float HudRightGap = 7f; private const float MaximumLabelWidth = 190f; private const float MinimumBarWidth = 100f; private const float MaximumBarWidth = 220f; private const float BarHeight = 20f; private const float MaximumHudWidth = 450f; private const float HealthLogBaseline = 100f; private const float HealthLogReference = 1000f; private const float SlowHealthDelaySeconds = 0.35f; private const float SlowHealthDrainPerSecond = 0.6f; private const float SafeAreaGap = 6f; private const float HeaderDragThresholdPixels = 5f; private static readonly Vector2 DefaultNormalizedPosition = new Vector2(0.015f, 0.32f); private static readonly Color HeaderToggleColor = new Color(1f, 0.52f, 0.16f, 0.72f); private static readonly Color HeaderToggleHoverColor = new Color(1f, 0.68f, 0.28f, 0.96f); private static readonly Color HeaderTogglePressedColor = new Color(0.78f, 0.32f, 0.08f, 0.92f); private static readonly Color HeaderNameColor = ClanUiFactory.GetClanColor(); private static readonly Color HeaderNameHoverColor = Color.Lerp(HeaderNameColor, Color.white, 0.35f); private static readonly Color HealthBackgroundColor = new Color(0f, 0f, 0f, 0.484f); private static readonly Color HealthSlowColor = new Color(1f, 0.8482759f, 0f, 1f); private static readonly Color HealthFastColor = new Color(1f, 0.333f, 0.333f, 1f); private static readonly Color RowTextColor = new Color(0.96f, 0.93f, 0.84f, 1f); private static readonly List UiRaycastResults = new List(); private static GameObject? _root; private static RectTransform? _rootRect; private static RectTransform? _headerRect; private static Text? _headerClanName; private static Image? _headerDragSurface; private static RectTransform? _collapseButtonRect; private static Image? _collapseIcon; private static HudRowView[] _rows = Array.Empty(); private static float _nextRequestTime; private static string _displayedClanId = ""; private static int _lastScreenWidth; private static int _lastScreenHeight; private static Rect _lastSafeArea; private static Vector2 _lastHudScale; private static bool _isWritingPosition; private static HudPointerMode _pointerMode; private static bool _restoreChatFocusAfterPointer; private static Vector2 _headerPressPosition; private static Vector2 _dragPointerOffset; private static Camera? _dragCamera; private static Transform? _trackedHudParent; private static bool _lastHudParentActive; private static Vector2 _lastHudParentSize; private static EventSystem? _uiRaycastEventSystem; private static PointerEventData? _uiRaycastPointerData; public static void Init() { GUIManager.OnCustomGUIAvailable += Rebuild; ClanRpc.SnapshotChanged += OnSnapshotChanged; ClanRpc.HudSnapshotChanged += OnHudSnapshotChanged; ClanPlugin.ClanHudPosition.SettingChanged += OnHudPositionChanged; ClanPlugin.ClanHudPlayerListCollapsed.SettingChanged += OnPlayerListCollapsedChanged; ClanPlugin.ShowClanHud.SettingChanged += OnShowHudChanged; ClanLocalization.LanguageChanged += OnLanguageChanged; Rebuild(); } public static void Dispose() { //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) GUIManager.OnCustomGUIAvailable -= Rebuild; ClanRpc.SnapshotChanged -= OnSnapshotChanged; ClanRpc.HudSnapshotChanged -= OnHudSnapshotChanged; ClanPlugin.ClanHudPosition.SettingChanged -= OnHudPositionChanged; ClanPlugin.ClanHudPlayerListCollapsed.SettingChanged -= OnPlayerListCollapsedChanged; ClanPlugin.ShowClanHud.SettingChanged -= OnShowHudChanged; ClanLocalization.LanguageChanged -= OnLanguageChanged; _pointerMode = HudPointerMode.None; _restoreChatFocusAfterPointer = false; _dragCamera = null; if ((Object)(object)_root != (Object)null) { _root.SetActive(false); Object.Destroy((Object)(object)_root); _root = null; } _rootRect = null; _headerRect = null; _headerClanName = null; _headerDragSurface = null; _collapseButtonRect = null; _collapseIcon = null; _rows = Array.Empty(); _nextRequestTime = 0f; _displayedClanId = ""; _lastScreenWidth = 0; _lastScreenHeight = 0; _lastSafeArea = default(Rect); _lastHudScale = Vector2.zero; _trackedHudParent = null; _lastHudParentActive = false; _lastHudParentSize = Vector2.zero; _uiRaycastEventSystem = null; _uiRaycastPointerData = null; UiRaycastResults.Clear(); } public static void Tick() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) Transform hudParent = GetHudParent(); if ((Object)(object)hudParent == (Object)null) { CancelPointerInteraction(saveDrag: false, restoreChatFocus: false); if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } _trackedHudParent = null; _lastHudParentActive = false; _lastHudParentSize = Vector2.zero; return; } if ((Object)(object)_root == (Object)null || (Object)(object)_root.transform.parent != (Object)(object)hudParent) { Rebuild(); if ((Object)(object)_root == (Object)null || (Object)(object)_root.transform.parent != (Object)(object)hudParent) { return; } } if (HudParentEnvironmentChanged(hudParent)) { _nextRequestTime = 0f; Refresh(); if (((Component)hudParent).gameObject.activeInHierarchy) { ApplyConfiguredPosition(); } } if (ScreenEnvironmentChanged() && _pointerMode != HudPointerMode.Drag) { ApplyConfiguredPosition(); } ClanClientSnapshot currentSnapshot = ClanRpc.CurrentSnapshot; bool num = ReconcileVisibility(); UpdateHeaderRaycastState(); HandleHudPointerInput(); if (!num) { _nextRequestTime = 0f; return; } if (IsPlayerListCollapsed()) { _nextRequestTime = 0f; return; } AnimateSlowHealth(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextRequestTime)) { _nextRequestTime = realtimeSinceStartup + 0.5f; ClanRpc.RequestHudSnapshot(currentSnapshot.ClanId); } } private static bool ShouldShow(ClanClientSnapshot snapshot) { return ShouldShow(snapshot, GetHudParent()); } private static bool ShouldShow(ClanClientSnapshot snapshot, Transform? hudParent) { if ((Object)(object)ZNet.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)hudParent != (Object)null && ((Component)hudParent).gameObject.activeInHierarchy && ClanPlugin.ShowClanHud.Value.IsOn()) { return snapshot.HasClan; } return false; } private static bool ReconcileVisibility() { ClanClientSnapshot currentSnapshot = ClanRpc.CurrentSnapshot; Transform hudParent = GetHudParent(); bool flag = ShouldShow(currentSnapshot, hudParent); if ((Object)(object)_root == (Object)null) { return flag; } bool num = _root.activeSelf != flag; bool flag2 = flag && !StringComparer.Ordinal.Equals(_displayedClanId, currentSnapshot.ClanId); if (!num && !flag2) { return flag; } _nextRequestTime = 0f; Refresh(); if (flag) { ApplyConfiguredPosition(); } return flag; } internal static bool CapturesChatPointer(Vector2 pointerPosition) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (_pointerMode != HudPointerMode.None) { return true; } if (!HasReleasedCursor() || !((Object)(object)_headerRect != (Object)null) || !((Component)_headerRect).gameObject.activeInHierarchy || !RectTransformUtility.RectangleContainsScreenPoint(_headerRect, pointerPosition, ClanUiFactory.GetCanvasCamera((Component?)(object)_headerRect))) { return false; } if (!InventoryGui.IsVisible()) { return IsHudTopmostAtPointer(pointerPosition); } return !IsPointerBlockedByVisibleInventoryPanel(pointerPosition); } private static void Refresh() { if ((Object)(object)_root == (Object)null) { return; } ClanClientSnapshot currentSnapshot = ClanRpc.CurrentSnapshot; bool flag = ShouldShow(currentSnapshot); _root.SetActive(flag); _displayedClanId = (flag ? currentSnapshot.ClanId : ""); if (!flag) { HideRows(); LayoutVisibleRows(0); return; } if ((Object)(object)_headerClanName != (Object)null) { _headerClanName.text = ClanLocalization.Format("clan_hud_header", ClanUiFactory.CleanSingleLine(currentSnapshot.ClanName)); } if (IsPlayerListCollapsed()) { HideRows(); LayoutVisibleRows(0); return; } ClanHudSnapshot currentHudSnapshot = ClanRpc.CurrentHudSnapshot; if (!StringComparer.Ordinal.Equals(currentHudSnapshot.ClanId, currentSnapshot.ClanId)) { HideRows(); LayoutVisibleRows(0); return; } int num = 0; foreach (ClanHudPlayerSummary player2 in currentHudSnapshot.Players) { if (num == _rows.Length) { break; } if (TryFindOnlineRosterPlayer(currentSnapshot, player2.PlayerId, out ClanPlayerSummary player)) { UpdateRow(_rows[num], player2, player); num++; } } for (int i = num; i < _rows.Length; i++) { _rows[i].Root.SetActive(false); } LayoutVisibleRows(num); } private static void OnLanguageChanged() { Refresh(); } private static void HideRows() { HudRowView[] rows = _rows; for (int i = 0; i < rows.Length; i++) { rows[i].Root.SetActive(false); } } private static bool TryFindOnlineRosterPlayer(ClanClientSnapshot snapshot, string playerId, out ClanPlayerSummary player) { foreach (ClanPlayerSummary item in snapshot.Roster) { if (item.IsOnline && StringComparer.Ordinal.Equals(item.Id, playerId)) { player = item; return true; } } player = null; return false; } private static void UpdateRow(HudRowView row, ClanHudPlayerSummary player, ClanPlayerSummary rosterPlayer) { bool initialize = !row.Root.activeSelf || !StringComparer.Ordinal.Equals(row.PlayerId, player.PlayerId); row.Root.SetActive(true); row.PlayerId = player.PlayerId; row.Label.text = ClanLocalization.Format("clan_hud_player", ClanLocalization.Role(rosterPlayer.Role), ClanUiFactory.CleanSingleLine(rosterPlayer.Name)); if (!player.HasHealth || !IsFinite(player.CurrentHealth) || !IsFinite(player.MaxHealth) || !(player.MaxHealth > 0f)) { row.BarWidth = 100f; SetHealthRatio(row, 0f, initialize: true); row.Health.text = "— / —"; } else { float maxHealth = player.MaxHealth; float num = Mathf.Clamp(player.CurrentHealth, 0f, maxHealth); row.BarWidth = GetLogScaledBarWidth(maxHealth); SetHealthRatio(row, Mathf.Clamp01(num / maxHealth), initialize); row.Health.text = FormatHealth(num) + "/" + FormatHealth(maxHealth); } } private static float GetLogScaledBarWidth(float maximumHealth) { float num = Mathf.Log(11f); float num2 = ((num <= 0f) ? 0f : (Mathf.Log(1f + maximumHealth / 100f) / num)); return Mathf.Ceil(Mathf.Lerp(100f, 220f, Mathf.Clamp01(num2))); } private static void SetHealthRatio(HudRowView row, float ratio, bool initialize) { ratio = Mathf.Clamp01(ratio); float fastRatio = row.FastRatio; row.FastRatio = ratio; SetFill(row.FastFill, ratio); if (initialize) { row.SlowRatio = ratio; row.SlowTargetRatio = ratio; row.SlowDelayUntil = 0f; SetFill(row.SlowFill, ratio); return; } if (ratio > fastRatio + 0.0001f) { row.SlowRatio = ratio; row.SlowTargetRatio = ratio; row.SlowDelayUntil = 0f; SetFill(row.SlowFill, ratio); return; } if (ratio < fastRatio - 0.0001f) { row.SlowRatio = Mathf.Max(row.SlowRatio, fastRatio); row.SlowDelayUntil = Time.unscaledTime + 0.35f; } row.SlowTargetRatio = ratio; } private static void AnimateSlowHealth() { float unscaledTime = Time.unscaledTime; float num = 0.6f * Time.unscaledDeltaTime; HudRowView[] rows = _rows; foreach (HudRowView hudRowView in rows) { if (hudRowView.Root.activeSelf && !(unscaledTime < hudRowView.SlowDelayUntil) && !(hudRowView.SlowRatio <= hudRowView.SlowTargetRatio)) { hudRowView.SlowRatio = Mathf.MoveTowards(hudRowView.SlowRatio, hudRowView.SlowTargetRatio, num); SetFill(hudRowView.SlowFill, hudRowView.SlowRatio); } } } private static void SetFill(RectTransform fill, float ratio) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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) fill.anchorMin = Vector2.zero; fill.anchorMax = new Vector2(Mathf.Clamp01(ratio), 1f); fill.offsetMin = Vector2.zero; fill.offsetMax = Vector2.zero; } private static string FormatHealth(float health) { return Mathf.Ceil(health).ToString("0", CultureInfo.InvariantCulture); } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static void Rebuild() { Rebuild(GetHudParent()); } private static void Rebuild(Transform? hudParent) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) if (!GUIManager.IsHeadless() && !((Object)(object)hudParent == (Object)null)) { CancelPointerInteraction(saveDrag: false, restoreChatFocus: false); if ((Object)(object)_root != (Object)null) { _root.SetActive(false); Object.Destroy((Object)(object)_root); } Sprite healthSprite = null; Material healthMaterial = null; try { healthSprite = GUIManager.Instance.GetSprite("bar_gradient"); } catch (Exception) { } try { healthMaterial = Cache.GetPrefab("lithud"); } catch (Exception) { } _root = ClanUiFactory.CreateObject("ClanMemberHud", hudParent); _root.SetActive(false); _rootRect = _root.GetComponent(); _rootRect.anchorMin = new Vector2(0f, 1f); _rootRect.anchorMax = new Vector2(0f, 1f); _rootRect.pivot = new Vector2(0f, 1f); _rootRect.anchoredPosition = new Vector2(18f, -220f); _rootRect.sizeDelta = new Vector2(0f, 42f); CreateHeader(_root.transform); _rows = new HudRowView[10]; for (int i = 0; i < _rows.Length; i++) { _rows[i] = CreateMemberRow(_root.transform, i, healthSprite, healthMaterial); _rows[i].Root.SetActive(false); } _lastScreenWidth = 0; _lastScreenHeight = 0; _lastSafeArea = default(Rect); _lastHudScale = Vector2.zero; _displayedClanId = ""; _nextRequestTime = 0f; _trackedHudParent = hudParent; _lastHudParentActive = false; _lastHudParentSize = Vector2.zero; Refresh(); ApplyConfiguredPosition(); } } private static void CreateHeader(Transform parent) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) GameObject val = ClanUiFactory.CreateObject("HudHeader", parent, typeof(Image)); _headerRect = val.GetComponent(); _headerDragSurface = val.GetComponent(); ((Graphic)_headerDragSurface).color = Color.clear; ((Graphic)_headerDragSurface).raycastTarget = HasReleasedCursor(); _headerClanName = CreateText(val.transform, ClanLocalization.Text("clan_hud_title"), 24, (TextAnchor)3, HeaderNameColor, bold: true); _headerClanName.resizeTextForBestFit = true; _headerClanName.resizeTextMinSize = 18; _headerClanName.resizeTextMaxSize = 24; SetTopLeftRect(((Graphic)_headerClanName).rectTransform, 0f, 0f, 0f, 42f); AddTextOutline(_headerClanName); GameObject val2 = ClanUiFactory.CreateObject("HudPlayerListToggle", val.transform); _collapseButtonRect = val2.GetComponent(); SetTopLeftRect(_collapseButtonRect, 0f, 8f, 26f, 26f); GameObject obj = ClanUiFactory.CreateObject("Icon", val2.transform, typeof(Image)); RectTransform component = obj.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.anchoredPosition = Vector2.zero; component.sizeDelta = new Vector2(14f, 14f); _collapseIcon = obj.GetComponent(); _collapseIcon.sprite = ClanUiFeedback.GetIcon(ClanActionIcon.Collapse); _collapseIcon.preserveAspect = true; ((Graphic)_collapseIcon).raycastTarget = false; ((Graphic)_collapseIcon).color = HeaderToggleColor; RefreshCollapseButton(); } private static HudRowView CreateMemberRow(Transform parent, int index, Sprite? healthSprite, Material? healthMaterial) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) GameObject val = ClanUiFactory.CreateObject($"HudMemberRow{index}", parent); RectTransform component = val.GetComponent(); Text val2 = CreateText(val.transform, "", 13, (TextAnchor)3, RowTextColor, bold: true); val2.resizeTextForBestFit = true; val2.resizeTextMinSize = 10; val2.resizeTextMaxSize = 13; AddTextOutline(val2); GameObject val3 = ClanUiFactory.CreateObject("HealthBar", val.transform, typeof(Image)); RectTransform component2 = val3.GetComponent(); Image component3 = val3.GetComponent(); ((Graphic)component3).color = HealthBackgroundColor; ((Graphic)component3).raycastTarget = false; RectTransform component4 = ClanUiFactory.CreateObject("FillArea", val3.transform).GetComponent(); Stretch(component4, 2f, 2f, 2f, 2f); GameObject obj = ClanUiFactory.CreateObject("HealthSlow", (Transform)(object)component4, typeof(Image)); Image component5 = obj.GetComponent(); ((Graphic)component5).color = HealthSlowColor; ((Graphic)component5).raycastTarget = false; ApplyVanillaHealthStyle(component5, healthSprite, healthMaterial); RectTransform component6 = obj.GetComponent(); SetFill(component6, 0f); GameObject obj2 = ClanUiFactory.CreateObject("HealthFast", (Transform)(object)component4, typeof(Image)); Image component7 = obj2.GetComponent(); ((Graphic)component7).color = HealthFastColor; ((Graphic)component7).raycastTarget = false; ApplyVanillaHealthStyle(component7, healthSprite, healthMaterial); RectTransform component8 = obj2.GetComponent(); SetFill(component8, 0f); Text val4 = CreateText(val3.transform, "— / —", 12, (TextAnchor)4, Color.white, bold: true); val4.resizeTextForBestFit = true; val4.resizeTextMinSize = 9; val4.resizeTextMaxSize = 12; Stretch(((Graphic)val4).rectTransform, 3f, 3f, 0f, 0f); AddTextOutline(val4); return new HudRowView { Root = val, Rect = component, Label = val2, Bar = component2, SlowFill = component6, FastFill = component8, Health = val4 }; } private static void ApplyVanillaHealthStyle(Image image, Sprite? sprite, Material? material) { if ((Object)(object)sprite != (Object)null) { image.sprite = sprite; image.type = (Type)2; image.pixelsPerUnitMultiplier = 2f; } if ((Object)(object)material != (Object)null) { ((Graphic)image).material = material; } } private static void LayoutVisibleRows(int visibleCount) { //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_rootRect == (Object)null) && !((Object)(object)_headerRect == (Object)null)) { visibleCount = Mathf.Clamp(visibleCount, 0, _rows.Length); float num = 0f; for (int i = 0; i < visibleCount; i++) { HudRowView hudRowView = _rows[i]; num = Mathf.Max(num, Mathf.Min(190f, Mathf.Ceil(MeasureNaturalWidth(hudRowView.Label)))); } float num2 = Mathf.Max(0f, 410f); float num3 = (((Object)(object)_headerClanName == (Object)null) ? 0f : Mathf.Clamp(Mathf.Ceil(MeasureNaturalWidth(_headerClanName)), 0f, num2)); float num4 = num3 + 7f + 26f + 7f; float num5 = num4; for (int j = 0; j < visibleCount; j++) { float num6 = num + 7f + _rows[j].BarWidth + 7f; num5 = Mathf.Max(num5, num6); } num5 = Mathf.Min(num5, 450f); float num7 = ((visibleCount == 0) ? 0f : (5f + (float)visibleCount * 26f + (float)(visibleCount - 1) * 3f)); float num8 = 42f + num7; Rect rect = _rootRect.rect; int num9; if (!(Mathf.Abs(((Rect)(ref rect)).width - num5) > 0.1f)) { rect = _rootRect.rect; num9 = ((Mathf.Abs(((Rect)(ref rect)).height - num8) > 0.1f) ? 1 : 0); } else { num9 = 1; } bool flag = (byte)num9 != 0; _rootRect.sizeDelta = new Vector2(num5, num8); SetTopLeftRect(_headerRect, 0f, 0f, num4, 42f); if ((Object)(object)_headerClanName != (Object)null) { SetTopLeftRect(((Graphic)_headerClanName).rectTransform, 0f, 0f, num3, 42f); } if ((Object)(object)_collapseButtonRect != (Object)null) { SetTopLeftRect(_collapseButtonRect, num3 + 7f, 8f, 26f, 26f); } float num10 = 47f; for (int k = 0; k < visibleCount; k++) { HudRowView hudRowView2 = _rows[k]; float top = num10 + (float)k * 29f; float width = num + 7f + hudRowView2.BarWidth + 7f; SetTopLeftRect(hudRowView2.Rect, 0f, top, width, 26f); SetTopLeftRect(((Graphic)hudRowView2.Label).rectTransform, 0f, 0f, num, 26f); SetTopLeftRect(hudRowView2.Bar, num + 7f, 3f, hudRowView2.BarWidth, 20f); } if (flag && _pointerMode != HudPointerMode.Drag) { ApplyConfiguredPosition(); } } } private static void SetTopLeftRect(RectTransform rect, float left, float top, float width, float height) { //IL_000b: 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) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0f, 1f); rect.anchorMax = new Vector2(0f, 1f); rect.pivot = new Vector2(0f, 1f); rect.anchoredPosition = new Vector2(left, 0f - top); rect.sizeDelta = new Vector2(width, height); } private static float MeasureNaturalWidth(Text text) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) TextGenerationSettings generationSettings = text.GetGenerationSettings(Vector2.zero); generationSettings.resizeTextForBestFit = false; generationSettings.horizontalOverflow = (HorizontalWrapMode)1; return text.cachedTextGeneratorForLayout.GetPreferredWidth(text.text, generationSettings) / Mathf.Max(0.0001f, text.pixelsPerUnit); } private static void Stretch(RectTransform rect, float left, float right, float bottom, float top) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2(left, bottom); rect.offsetMax = new Vector2(0f - right, 0f - top); } private static void OnSnapshotChanged(ClanClientSnapshot snapshot) { if (!StringComparer.Ordinal.Equals(_displayedClanId, snapshot.ClanId)) { _nextRequestTime = 0f; } Refresh(); if (ShouldShow(snapshot)) { ApplyConfiguredPosition(); } } private static void OnHudSnapshotChanged(ClanHudSnapshot snapshot) { Refresh(); } private static void OnHudPositionChanged(object sender, EventArgs args) { if (_pointerMode != HudPointerMode.Drag && !_isWritingPosition) { ApplyConfiguredPosition(); } } private static void OnPlayerListCollapsedChanged(object sender, EventArgs args) { _nextRequestTime = 0f; RefreshCollapseButton(); Refresh(); } private static void OnShowHudChanged(object sender, EventArgs args) { _nextRequestTime = 0f; Refresh(); ApplyConfiguredPosition(); } private static bool IsPlayerListCollapsed() { return ClanPlugin.ClanHudPlayerListCollapsed.Value.IsOn(); } private static void TogglePlayerListCollapsed() { ClanPlugin.ClanHudPlayerListCollapsed.Value = ((!IsPlayerListCollapsed()) ? ClanPlugin.Toggle.On : ClanPlugin.Toggle.Off); } private static void RefreshCollapseButton() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) bool flag = IsPlayerListCollapsed(); if ((Object)(object)_collapseIcon != (Object)null) { _collapseIcon.sprite = ClanUiFeedback.GetIcon(flag ? ClanActionIcon.Expand : ClanActionIcon.Collapse); ((Graphic)_collapseIcon).color = HeaderToggleColor; } } private static bool HudParentEnvironmentChanged(Transform hudParent) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) bool flag = (Object)(object)_trackedHudParent != (Object)(object)hudParent; bool activeInHierarchy = ((Component)hudParent).gameObject.activeInHierarchy; RectTransform val = (RectTransform)(object)((hudParent is RectTransform) ? hudParent : null); Vector2 val2; if (val == null) { val2 = Vector2.zero; } else { Rect rect = val.rect; val2 = ((Rect)(ref rect)).size; } Vector2 val3 = val2; bool flag2 = activeInHierarchy && (flag || !_lastHudParentActive); int num; if (activeInHierarchy) { if (!flag) { Vector2 val4 = val3 - _lastHudParentSize; num = ((((Vector2)(ref val4)).sqrMagnitude > 0.25f) ? 1 : 0); } else { num = 1; } } else { num = 0; } bool flag3 = (byte)num != 0; _trackedHudParent = hudParent; _lastHudParentActive = activeInHierarchy; _lastHudParentSize = val3; return flag2 || flag3; } private static bool ScreenEnvironmentChanged() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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) Rect safeArea = GetSafeArea(); Vector3 val = (((Object)(object)_rootRect == (Object)null) ? Vector3.one : ((Transform)_rootRect).lossyScale); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(Mathf.Abs(val.x), Mathf.Abs(val.y)); int result; if (_lastScreenWidth == Screen.width && _lastScreenHeight == Screen.height && ClanUiFactory.RectApproximately(_lastSafeArea, safeArea)) { Vector2 val3 = _lastHudScale - val2; result = ((((Vector2)(ref val3)).sqrMagnitude > 1E-06f) ? 1 : 0); } else { result = 1; } _lastScreenWidth = Screen.width; _lastScreenHeight = Screen.height; _lastSafeArea = safeArea; _lastHudScale = val2; return (byte)result != 0; } private static void ApplyConfiguredPosition() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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) if (!((Object)(object)_rootRect == (Object)null)) { Transform parent = ((Transform)_rootRect).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); if (val != null) { Vector2 val2 = SanitizeNormalizedPosition(ClanPlugin.ClanHudPosition.Value); Canvas.ForceUpdateCanvases(); Camera canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)_rootRect); Rect safeArea = GetSafeArea(); Vector2 screenPosition = default(Vector2); ((Vector2)(ref screenPosition))..ctor(((Rect)(ref safeArea)).xMin + val2.x * ((Rect)(ref safeArea)).width, ((Rect)(ref safeArea)).yMax - val2.y * ((Rect)(ref safeArea)).height); SetRootPivotScreenPosition(val, screenPosition, canvasCamera); ClampToSafeArea(val, safeArea, canvasCamera); } } } private static Vector2 SanitizeNormalizedPosition(Vector2 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!IsFinite(value.x) || !IsFinite(value.y)) { return DefaultNormalizedPosition; } return new Vector2(Mathf.Clamp01(value.x), Mathf.Clamp01(value.y)); } private static bool CanStartDrag() { if ((Object)(object)_root != (Object)null && _root.activeInHierarchy) { if (_pointerMode != HudPointerMode.HeaderPending) { return _pointerMode == HudPointerMode.Drag; } return true; } return false; } private static bool HasReleasedCursor() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if (Cursor.visible) { return (int)Cursor.lockState != 1; } return false; } private static void UpdateHeaderRaycastState() { bool raycastTarget = HasReleasedCursor() || _pointerMode != HudPointerMode.None; if ((Object)(object)_headerDragSurface != (Object)null) { ((Graphic)_headerDragSurface).raycastTarget = raycastTarget; } } private static void HandleHudPointerInput() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null || !_root.activeInHierarchy || (Object)(object)_headerRect == (Object)null || !((Component)_headerRect).gameObject.activeInHierarchy) { CancelPointerInteraction(saveDrag: false, restoreChatFocus: false); return; } Vector2 val = Vector2.op_Implicit(Input.mousePosition); Camera canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)_headerRect); bool flag = RectTransformUtility.RectangleContainsScreenPoint(_headerRect, val, canvasCamera); bool flag2 = (Object)(object)_collapseButtonRect != (Object)null && ((Component)_collapseButtonRect).gameObject.activeInHierarchy && RectTransformUtility.RectangleContainsScreenPoint(_collapseButtonRect, val, ClanUiFactory.GetCanvasCamera((Component?)(object)_collapseButtonRect)); bool flag3 = InventoryGui.IsVisible(); bool flag4 = HasReleasedCursor() && (!flag3 || !IsPointerBlockedByVisibleInventoryPanel(val)) && (flag3 || IsHudTopmostAtPointer(val)); bool flag5 = (Object)(object)_headerClanName != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(((Graphic)_headerClanName).rectTransform, val, ClanUiFactory.GetCanvasCamera((Component?)(object)((Graphic)_headerClanName).rectTransform)); if ((Object)(object)_headerClanName != (Object)null) { ((Graphic)_headerClanName).color = ((flag4 && flag5) ? HeaderNameHoverColor : HeaderNameColor); } if ((Object)(object)_collapseIcon != (Object)null) { ((Graphic)_collapseIcon).color = ((_pointerMode == HudPointerMode.Collapse) ? HeaderTogglePressedColor : ((_pointerMode == HudPointerMode.None && flag4 && flag2) ? HeaderToggleHoverColor : HeaderToggleColor)); } if (_pointerMode == HudPointerMode.None) { if (Input.GetMouseButtonDown(0) && flag4 && flag) { _pointerMode = (flag2 ? HudPointerMode.Collapse : HudPointerMode.HeaderPending); _restoreChatFocusAfterPointer = !flag3 && ClanVanillaChatDock.HasFocusedChatInputForHud(); _headerPressPosition = val; if ((Object)(object)_collapseIcon != (Object)null && flag2) { ((Graphic)_collapseIcon).color = HeaderTogglePressedColor; } UpdateHeaderRaycastState(); } return; } if (Input.GetMouseButton(0)) { if (_pointerMode == HudPointerMode.HeaderPending) { Vector2 val2 = val - _headerPressPosition; if (((Vector2)(ref val2)).sqrMagnitude >= 25f && BeginDrag(val, canvasCamera)) { _pointerMode = HudPointerMode.Drag; } } if (_pointerMode == HudPointerMode.Drag) { Drag(val); } return; } bool num = _pointerMode == HudPointerMode.Collapse && Input.GetMouseButtonUp(0) && flag2 && (!InventoryGui.IsVisible() || !IsPointerBlockedByVisibleInventoryPanel(val)); bool saveDrag = _pointerMode == HudPointerMode.Drag; bool restoreChatFocusAfterPointer = _restoreChatFocusAfterPointer; CancelPointerInteraction(saveDrag, restoreChatFocus: false); if (num) { TogglePlayerListCollapsed(); } if (restoreChatFocusAfterPointer && !InventoryGui.IsVisible()) { ClanVanillaChatDock.RestoreChatInputAfterHudInteraction(); } } private static void CancelPointerInteraction(bool saveDrag, bool restoreChatFocus) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) bool num = restoreChatFocus && _restoreChatFocusAfterPointer; if (_pointerMode == HudPointerMode.Drag) { EndDrag(saveDrag); } _pointerMode = HudPointerMode.None; _restoreChatFocusAfterPointer = false; _dragCamera = null; if ((Object)(object)_collapseIcon != (Object)null) { ((Graphic)_collapseIcon).color = HeaderToggleColor; } UpdateHeaderRaycastState(); if (num && !InventoryGui.IsVisible()) { ClanVanillaChatDock.RestoreChatInputAfterHudInteraction(); } } private static bool IsPointerBlockedByVisibleInventoryPanel(Vector2 pointerPosition) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!InventoryGui.IsVisible()) { return false; } InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null) { return true; } if (!ContainsPointerInActivePanel((Component?)(object)instance.m_player, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_crafting, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_info, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_container, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_variantDialog, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_skillsDialog, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_textsDialog, pointerPosition) && !ContainsPointerInActivePanel((Component?)(object)instance.m_splitPanel, pointerPosition)) { return ContainsPointerInActivePanel(instance.m_trophiesPanel, pointerPosition); } return true; } private static bool ContainsPointerInActivePanel(Component? panel, Vector2 pointerPosition) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)panel == (Object)null) { return false; } return ContainsPointerInActivePanel(panel.gameObject, pointerPosition); } private static bool ContainsPointerInActivePanel(GameObject? panel, Vector2 pointerPosition) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)panel == (Object)null) && panel.activeInHierarchy) { Transform transform = panel.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val != null) { return RectTransformUtility.RectangleContainsScreenPoint(val, pointerPosition, ClanUiFactory.GetCanvasCamera((Component?)(object)val)); } } return false; } private static bool IsHudTopmostAtPointer(Vector2 pointerPosition) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null || !_root.activeInHierarchy) { return false; } EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null || !((Behaviour)current).isActiveAndEnabled) { return false; } if ((Object)(object)_uiRaycastEventSystem != (Object)(object)current || _uiRaycastPointerData == null) { _uiRaycastEventSystem = current; _uiRaycastPointerData = new PointerEventData(current); } else { ((AbstractEventData)_uiRaycastPointerData).Reset(); } _uiRaycastPointerData.position = pointerPosition; _uiRaycastPointerData.button = (InputButton)0; UiRaycastResults.Clear(); current.RaycastAll(_uiRaycastPointerData, UiRaycastResults); bool result = false; foreach (RaycastResult uiRaycastResult in UiRaycastResults) { RaycastResult current2 = uiRaycastResult; if (current2.module is GraphicRaycaster && !((Object)(object)((RaycastResult)(ref current2)).gameObject == (Object)null)) { Transform transform = ((RaycastResult)(ref current2)).gameObject.transform; result = (Object)(object)transform == (Object)(object)_root.transform || transform.IsChildOf(_root.transform); break; } } UiRaycastResults.Clear(); return result; } private static bool BeginDrag(Vector2 pointerPosition, Camera? eventCamera) { //IL_0034: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_0046: Unknown result type (might be due to invalid IL or missing references) if (!CanStartDrag() || (Object)(object)_rootRect == (Object)null) { return false; } _dragCamera = eventCamera ?? ClanUiFactory.GetCanvasCamera((Component?)(object)_rootRect); Vector2 val = RectTransformUtility.WorldToScreenPoint(_dragCamera, ((Transform)_rootRect).position); _dragPointerOffset = pointerPosition - val; return true; } private static void Drag(Vector2 pointerPosition) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (_pointerMode == HudPointerMode.Drag && !((Object)(object)_rootRect == (Object)null)) { Transform parent = ((Transform)_rootRect).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); if (val != null) { Vector2 screenPosition = pointerPosition - _dragPointerOffset; SetRootPivotScreenPosition(val, screenPosition, _dragCamera); ClampToSafeArea(val, GetSafeArea(), _dragCamera); } } } private static void EndDrag(bool save) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (_pointerMode != HudPointerMode.Drag) { return; } if ((Object)(object)_rootRect != (Object)null) { Transform parent = ((Transform)_rootRect).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); if (val != null) { ClampToSafeArea(val, GetSafeArea(), _dragCamera); } } _dragCamera = null; if (save) { SaveCurrentPosition(); } } private static void SaveCurrentPosition() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rootRect == (Object)null) { return; } Canvas.ForceUpdateCanvases(); Camera canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)_rootRect); Rect safeArea = GetSafeArea(); Rect screenBounds = ClanUiFactory.GetScreenBounds(_rootRect, canvasCamera); Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor((((Rect)(ref safeArea)).width <= 0f) ? 0f : ((((Rect)(ref screenBounds)).xMin - ((Rect)(ref safeArea)).xMin) / ((Rect)(ref safeArea)).width), (((Rect)(ref safeArea)).height <= 0f) ? 0f : ((((Rect)(ref safeArea)).yMax - ((Rect)(ref screenBounds)).yMax) / ((Rect)(ref safeArea)).height)); val = SanitizeNormalizedPosition(val); Vector2 val2 = ClanPlugin.ClanHudPosition.Value - val; if (((Vector2)(ref val2)).sqrMagnitude <= 1E-06f) { return; } _isWritingPosition = true; try { ClanPlugin.ClanHudPosition.Value = val; } finally { _isWritingPosition = false; } } private static void ClampToSafeArea(RectTransform parentRect, Rect safeArea, Camera? camera) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_rootRect == (Object)null)) { Rect screenBounds = ClanUiFactory.GetScreenBounds(_rootRect, camera); float num = ((((Rect)(ref screenBounds)).width > ((Rect)(ref safeArea)).width) ? (((Rect)(ref safeArea)).xMin - ((Rect)(ref screenBounds)).xMin) : ((((Rect)(ref screenBounds)).xMin < ((Rect)(ref safeArea)).xMin) ? (((Rect)(ref safeArea)).xMin - ((Rect)(ref screenBounds)).xMin) : ((((Rect)(ref screenBounds)).xMax > ((Rect)(ref safeArea)).xMax) ? (((Rect)(ref safeArea)).xMax - ((Rect)(ref screenBounds)).xMax) : 0f))); float num2 = ((((Rect)(ref screenBounds)).height > ((Rect)(ref safeArea)).height) ? (((Rect)(ref safeArea)).yMax - ((Rect)(ref screenBounds)).yMax) : ((((Rect)(ref screenBounds)).yMin < ((Rect)(ref safeArea)).yMin) ? (((Rect)(ref safeArea)).yMin - ((Rect)(ref screenBounds)).yMin) : ((((Rect)(ref screenBounds)).yMax > ((Rect)(ref safeArea)).yMax) ? (((Rect)(ref safeArea)).yMax - ((Rect)(ref screenBounds)).yMax) : 0f))); if (!(Mathf.Abs(num) <= 0.01f) || !(Mathf.Abs(num2) <= 0.01f)) { Vector2 val = RectTransformUtility.WorldToScreenPoint(camera, ((Transform)_rootRect).position); SetRootPivotScreenPosition(parentRect, val + new Vector2(num, num2), camera); } } } private static void SetRootPivotScreenPosition(RectTransform parentRect, Vector2 screenPosition, Camera? camera) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Vector3 position = default(Vector3); if ((Object)(object)_rootRect != (Object)null && RectTransformUtility.ScreenPointToWorldPointInRectangle(parentRect, screenPosition, camera, ref position)) { ((Transform)_rootRect).position = position; } } private static Rect GetSafeArea() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Rect result = Screen.safeArea; if (((Rect)(ref result)).width <= 0f || ((Rect)(ref result)).height <= 0f) { ((Rect)(ref result))..ctor(0f, 0f, (float)Screen.width, (float)Screen.height); } if (((Rect)(ref result)).width > 12f && ((Rect)(ref result)).height > 12f) { result = Rect.MinMaxRect(((Rect)(ref result)).xMin + 6f, ((Rect)(ref result)).yMin + 6f, ((Rect)(ref result)).xMax - 6f, ((Rect)(ref result)).yMax - 6f); } return result; } private static Transform? GetHudParent() { Hud instance = Hud.instance; if (!((Object)(object)instance != (Object)null) || !((Object)(object)instance.m_rootObject != (Object)null)) { return null; } return instance.m_rootObject.transform; } private static Text CreateText(Transform parent, string value, int size, TextAnchor anchor, Color color, bool bold) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) Text component = ClanUiFactory.CreateObject("Text", parent, typeof(Text)).GetComponent(); component.text = value; component.font = (bold ? GUIManager.Instance.AveriaSerifBold : GUIManager.Instance.AveriaSerif); component.fontSize = size; component.alignment = anchor; ((Graphic)component).color = color; ((Graphic)component).raycastTarget = false; component.horizontalOverflow = (HorizontalWrapMode)0; component.verticalOverflow = (VerticalWrapMode)0; return component; } private static void AddTextOutline(Text text) { //IL_0020: 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) Outline obj = ((Component)text).gameObject.AddComponent(); ((Shadow)obj).effectColor = new Color(0f, 0f, 0f, 0.78f); ((Shadow)obj).effectDistance = new Vector2(1f, -1f); ((Shadow)obj).useGraphicAlpha = true; } } internal static class ClanGifDecoder { private enum DisposalMethod { NotSpecified, DoNotDispose, RestoreToBackground, RestoreToPrevious } internal sealed class Frame { public int DelayMilliseconds { get; } public bool RequiresUserInput { get; } public byte[] Rgba32 { get; } internal Frame(int delayMilliseconds, bool requiresUserInput, byte[] rgba32) { DelayMilliseconds = delayMilliseconds; RequiresUserInput = requiresUserInput; Rgba32 = rgba32; } } internal sealed class Animation { public int Width { get; } public int Height { get; } public bool LoopsForever { get; } public IReadOnlyList Frames { get; } internal Animation(int width, int height, bool loopsForever, List frames) { Width = width; Height = height; LoopsForever = loopsForever; Frames = frames.AsReadOnly(); } } private sealed class Decoder { private readonly Reader _reader; private readonly int _maximumDimension; private readonly int _maximumFrames; private readonly long _maximumDecodedPixels; private readonly List _frames = new List(); private int _width; private int _height; private int _backgroundColorIndex; private long _canvasPixels; private byte[]? _globalColorTable; private byte[] _canvas = Array.Empty(); private bool _canvasInitialized; private int? _loopCount; private bool _hasPendingControl; private GraphicControl _pendingControl; private bool _hasPreviousFrame; private DisposalMethod _previousDisposal; private int _previousLeft; private int _previousTop; private int _previousWidth; private int _previousHeight; private bool _previousHadTransparency; private int _previousTransparentColorIndex; private byte[]? _previousRestoreCanvas; public Decoder(byte[] data, int maximumDimension, int maximumFrames, long maximumDecodedPixels) { _reader = new Reader(data); _maximumDimension = maximumDimension; _maximumFrames = maximumFrames; _maximumDecodedPixels = maximumDecodedPixels; } public Animation Decode() { ReadLogicalScreen(); while (true) { byte b = _reader.ReadByte("GIF block introducer"); switch (b) { case 44: ReadImage(); break; case 33: ReadExtension(); break; case 59: return Finish(); default: throw new InvalidDataException($"GIF contains unsupported block introducer 0x{b:x2}."); } } } private void ReadLogicalScreen() { string x = _reader.ReadAscii(6, "GIF signature"); if (!StringComparer.Ordinal.Equals(x, "GIF87a") && !StringComparer.Ordinal.Equals(x, "GIF89a")) { throw new InvalidDataException("File is not a GIF87a or GIF89a image."); } _width = _reader.ReadUInt16("logical-screen width"); _height = _reader.ReadUInt16("logical-screen height"); if (_width <= 0 || _height <= 0 || _width > _maximumDimension || _height > _maximumDimension) { throw new InvalidDataException($"GIF canvas must be 1-{_maximumDimension}px in each dimension."); } _canvasPixels = (long)_width * (long)_height; if (_canvasPixels > _maximumDecodedPixels || _canvasPixels > 536870911) { throw new InvalidDataException("GIF canvas exceeds the decoded-pixel limit."); } byte b = _reader.ReadByte("logical-screen flags"); _backgroundColorIndex = _reader.ReadByte("background color index"); _reader.ReadByte("pixel aspect ratio"); if ((b & 0x80) != 0) { int num = 1 << (b & 7) + 1; _globalColorTable = _reader.ReadColorTable(num, "global color table"); if (_backgroundColorIndex >= num) { throw new InvalidDataException("GIF background color index is outside the global color table."); } } _canvas = new byte[(int)_canvasPixels * 4]; } private void ReadExtension() { byte b = _reader.ReadByte("GIF extension label"); switch (b) { case 249: ReadGraphicControl(); break; case byte.MaxValue: ReadApplicationExtension(); break; case 254: _reader.SkipSubBlocks("GIF comment extension"); break; case 1: throw new InvalidDataException("GIF Plain Text Extension rendering is not supported."); default: _reader.SkipSubBlocks($"GIF extension 0x{b:x2}"); break; } } private void ReadGraphicControl() { if (_hasPendingControl) { throw new InvalidDataException("GIF contains multiple graphic controls for one rendering block."); } if (_reader.ReadByte("graphic-control block size") != 4) { throw new InvalidDataException("GIF graphic-control block must contain four bytes."); } byte b = _reader.ReadByte("graphic-control flags"); if ((b & 0xE0) != 0) { throw new InvalidDataException("GIF graphic-control block has nonzero reserved bits."); } int num = (b >> 2) & 7; if (num > 3) { throw new InvalidDataException($"GIF uses unsupported disposal method {num}."); } int delayCentiseconds = _reader.ReadUInt16("frame delay"); int transparentColorIndex = _reader.ReadByte("transparent color index"); if (_reader.ReadByte("graphic-control terminator") != 0) { throw new InvalidDataException("GIF graphic-control block has an invalid terminator."); } _pendingControl = new GraphicControl((DisposalMethod)num, (b & 2) != 0, (b & 1) != 0, transparentColorIndex, delayCentiseconds); _hasPendingControl = true; } private void ReadApplicationExtension() { int num = _reader.ReadByte("application-extension block size"); if (num != 11) { throw new InvalidDataException("GIF application identifier must contain eleven bytes."); } string text = _reader.ReadAscii(num, "application identifier"); if (!StringComparer.Ordinal.Equals(text, "NETSCAPE2.0") && !StringComparer.Ordinal.Equals(text, "ANIMEXTS1.0")) { _reader.SkipSubBlocks("GIF application '" + text + "'"); return; } if (_reader.ReadByte("loop-extension block size") != 3 || _reader.ReadByte("loop-extension identifier") != 1) { throw new InvalidDataException("GIF loop extension has an invalid data block."); } int num2 = _reader.ReadUInt16("loop count"); if (_reader.ReadByte("loop-extension terminator") != 0) { throw new InvalidDataException("GIF loop extension has unexpected extra data."); } if (_loopCount.HasValue && _loopCount.Value != num2) { throw new InvalidDataException("GIF contains conflicting loop extensions."); } _loopCount = num2; } private void ReadImage() { if (_frames.Count >= _maximumFrames) { throw new InvalidDataException($"GIF contains more than {_maximumFrames} frames."); } int num = _frames.Count + 1; if (_canvasPixels > _maximumDecodedPixels / num) { throw new InvalidDataException("GIF frames exceed the cumulative decoded-pixel limit."); } int num2 = _reader.ReadUInt16("image left"); int num3 = _reader.ReadUInt16("image top"); int num4 = _reader.ReadUInt16("image width"); int num5 = _reader.ReadUInt16("image height"); if (num4 <= 0 || num5 <= 0 || (long)num2 + (long)num4 > _width || (long)num3 + (long)num5 > _height) { throw new InvalidDataException("GIF image rectangle is empty or outside the logical screen."); } byte b = _reader.ReadByte("image flags"); if ((b & 0x18) != 0) { throw new InvalidDataException("GIF image descriptor has nonzero reserved bits."); } bool num6 = (b & 0x80) != 0; bool interlaced = (b & 0x40) != 0; byte[] array = _globalColorTable; if (num6) { int entryCount = 1 << (b & 7) + 1; array = _reader.ReadColorTable(entryCount, "local color table"); } if (array == null) { throw new InvalidDataException("GIF image does not have an active color table."); } GraphicControl graphicControl = (_hasPendingControl ? _pendingControl : GraphicControl.Default); _hasPendingControl = false; _pendingControl = default(GraphicControl); int num7 = array.Length / 3; if (graphicControl.HasTransparency && graphicControl.TransparentColorIndex >= num7) { throw new InvalidDataException("GIF transparent color index is outside the active color table."); } int num8 = _reader.ReadByte("LZW minimum code size"); if (num8 < 2 || num8 > 8) { throw new InvalidDataException("GIF LZW minimum code size must be between 2 and 8."); } long num9 = (long)num4 * (long)num5; if (num9 > int.MaxValue) { throw new InvalidDataException("GIF image rectangle is too large."); } byte[] indices = DecodeLzw(_reader, num8, (int)num9); EnsureCanvasInitialized(graphicControl); ApplyPreviousDisposal(); byte[] previousRestoreCanvas = ((graphicControl.Disposal == DisposalMethod.RestoreToPrevious) ? ((byte[])_canvas.Clone()) : null); DrawIndices(indices, array, graphicControl, num2, num3, num4, num5, interlaced); byte[] rgba = (byte[])_canvas.Clone(); _frames.Add(new Frame(graphicControl.DelayCentiseconds * 10, graphicControl.RequiresUserInput, rgba)); _hasPreviousFrame = true; _previousDisposal = graphicControl.Disposal; _previousLeft = num2; _previousTop = num3; _previousWidth = num4; _previousHeight = num5; _previousHadTransparency = graphicControl.HasTransparency; _previousTransparentColorIndex = graphicControl.TransparentColorIndex; _previousRestoreCanvas = previousRestoreCanvas; } private void EnsureCanvasInitialized(GraphicControl firstControl) { if (!_canvasInitialized) { if (_globalColorTable != null) { int num = _backgroundColorIndex * 3; byte alpha = (byte)((!firstControl.HasTransparency || firstControl.TransparentColorIndex != _backgroundColorIndex) ? byte.MaxValue : 0); FillCanvas(_globalColorTable[num], _globalColorTable[num + 1], _globalColorTable[num + 2], alpha); } _canvasInitialized = true; } } private void FillCanvas(byte red, byte green, byte blue, byte alpha) { int num = 0; while (num < _canvas.Length) { _canvas[num++] = red; _canvas[num++] = green; _canvas[num++] = blue; _canvas[num++] = alpha; } } private void ApplyPreviousDisposal() { if (!_hasPreviousFrame) { return; } switch (_previousDisposal) { case DisposalMethod.RestoreToBackground: RestorePreviousRectangleToBackground(); break; case DisposalMethod.RestoreToPrevious: if (_previousRestoreCanvas == null || _previousRestoreCanvas.Length != _canvas.Length) { throw new InvalidDataException("GIF restore-to-previous state is unavailable."); } Buffer.BlockCopy(_previousRestoreCanvas, 0, _canvas, 0, _canvas.Length); break; default: throw new InvalidDataException("GIF contains an invalid previous-frame disposal method."); case DisposalMethod.NotSpecified: case DisposalMethod.DoNotDispose: break; } _previousRestoreCanvas = null; } private void RestorePreviousRectangleToBackground() { byte b = 0; byte b2 = 0; byte b3 = 0; byte b4 = 0; if (_globalColorTable != null) { int num = _backgroundColorIndex * 3; b = _globalColorTable[num]; b2 = _globalColorTable[num + 1]; b3 = _globalColorTable[num + 2]; b4 = (byte)((!_previousHadTransparency || _previousTransparentColorIndex != _backgroundColorIndex) ? byte.MaxValue : 0); } for (int i = 0; i < _previousHeight; i++) { int num2 = ((_previousTop + i) * _width + _previousLeft) * 4; for (int j = 0; j < _previousWidth; j++) { _canvas[num2++] = b; _canvas[num2++] = b2; _canvas[num2++] = b3; _canvas[num2++] = b4; } } } private void DrawIndices(byte[] indices, byte[] colorTable, GraphicControl control, int left, int top, int width, int height, bool interlaced) { int sourceOffset = 0; if (!interlaced) { for (int i = 0; i < height; i++) { DrawRow(indices, ref sourceOffset, colorTable, control, left, top + i, width); } } else { int[] array = new int[4] { 0, 4, 2, 1 }; int[] array2 = new int[4] { 8, 8, 4, 2 }; for (int j = 0; j < array.Length; j++) { for (int k = array[j]; k < height; k += array2[j]) { DrawRow(indices, ref sourceOffset, colorTable, control, left, top + k, width); } } } if (sourceOffset != indices.Length) { throw new InvalidDataException("GIF interlace pass did not consume the decoded image."); } } private void DrawRow(byte[] indices, ref int sourceOffset, byte[] colorTable, GraphicControl control, int left, int destinationRow, int width) { int num = (destinationRow * _width + left) * 4; for (int i = 0; i < width; i++) { int num2 = indices[sourceOffset++]; if (control.HasTransparency && num2 == control.TransparentColorIndex) { num += 4; continue; } int num3 = num2 * 3; if (num3 > colorTable.Length - 3) { throw new InvalidDataException("GIF pixel references a color outside the active table."); } _canvas[num++] = colorTable[num3]; _canvas[num++] = colorTable[num3 + 1]; _canvas[num++] = colorTable[num3 + 2]; _canvas[num++] = byte.MaxValue; } } private Animation Finish() { if (_hasPendingControl) { throw new InvalidDataException("GIF ends with an unused graphic-control extension."); } if (_frames.Count == 0) { throw new InvalidDataException("GIF does not contain an image frame."); } if (!_reader.IsAtEnd) { throw new InvalidDataException("GIF contains trailing data after the trailer."); } return new Animation(_width, _height, _loopCount == 0, _frames); } } private readonly struct GraphicControl { public static readonly GraphicControl Default = new GraphicControl(DisposalMethod.NotSpecified, requiresUserInput: false, hasTransparency: false, 0, 0); public readonly DisposalMethod Disposal; public readonly bool RequiresUserInput; public readonly bool HasTransparency; public readonly int TransparentColorIndex; public readonly int DelayCentiseconds; public GraphicControl(DisposalMethod disposal, bool requiresUserInput, bool hasTransparency, int transparentColorIndex, int delayCentiseconds) { Disposal = disposal; RequiresUserInput = requiresUserInput; HasTransparency = hasTransparency; TransparentColorIndex = transparentColorIndex; DelayCentiseconds = delayCentiseconds; } } private sealed class Reader { private readonly byte[] _data; public int Position { get; private set; } public bool IsAtEnd => Position == _data.Length; public Reader(byte[] data) { _data = data; } public byte ReadByte(string context) { Require(1, context); return _data[Position++]; } public int ReadUInt16(string context) { Require(2, context); int result = _data[Position] | (_data[Position + 1] << 8); Position += 2; return result; } public string ReadAscii(int count, string context) { Require(count, context); string result = Encoding.ASCII.GetString(_data, Position, count); Position += count; return result; } public byte[] ReadColorTable(int entryCount, string context) { int num = entryCount * 3; Require(num, context); byte[] array = new byte[num]; Buffer.BlockCopy(_data, Position, array, 0, num); Position += num; return array; } public void SkipSubBlocks(string context) { while (true) { int num = ReadByte(context + " block size"); if (num == 0) { break; } Require(num, context); Position += num; } } private void Require(int count, string context) { if (count < 0 || Position > _data.Length - count) { throw new InvalidDataException("GIF ended while reading " + context + "."); } } } private sealed class SubBlockBitReader { private readonly Reader _reader; private int _remainingInBlock; private uint _bitBuffer; private int _bitCount; private bool _finished; public SubBlockBitReader(Reader reader) { _reader = reader; } public int ReadCode(int codeSize) { if (_finished) { throw new InvalidDataException("GIF LZW stream was read after its terminator."); } while (_bitCount < codeSize) { int num = ReadDataByte(); if (num < 0) { throw new InvalidDataException("GIF LZW stream ended before an end code."); } _bitBuffer |= (uint)(num << _bitCount); _bitCount += 8; } int num2 = (1 << codeSize) - 1; int result = (int)_bitBuffer & num2; _bitBuffer >>= codeSize; _bitCount -= codeSize; return result; } public void FinishAfterEndCode() { if (_finished) { throw new InvalidDataException("GIF LZW stream has multiple terminators."); } while (_remainingInBlock > 0) { _reader.ReadByte("LZW padding after end code"); _remainingInBlock--; } _reader.SkipSubBlocks("LZW padding after end code"); _finished = true; } private int ReadDataByte() { if (_remainingInBlock == 0) { int num = _reader.ReadByte("LZW sub-block size"); if (num == 0) { return -1; } _remainingInBlock = num; } _remainingInBlock--; return _reader.ReadByte("LZW image data"); } } private const int MaximumLzwCodeCount = 4096; public static Animation Decode(byte[] data, int maximumBytes, int maximumDimension, int maximumFrames, long maximumDecodedPixels) { if (data == null) { throw new InvalidDataException("GIF data is required."); } if (maximumBytes <= 0 || maximumDimension <= 0 || maximumFrames <= 0 || maximumDecodedPixels <= 0) { throw new InvalidDataException("GIF decode limits must be positive."); } if (data.Length > maximumBytes) { throw new InvalidDataException($"GIF size {data.Length} exceeds the {maximumBytes}-byte limit."); } return new Decoder(data, maximumDimension, maximumFrames, maximumDecodedPixels).Decode(); } private static byte[] DecodeLzw(Reader reader, int minimumCodeSize, int expectedPixelCount) { int num = 1 << minimumCodeSize; int num2 = num + 1; int num3 = num2 + 1; int num4 = minimumCodeSize + 1; int num5 = -1; byte b = 0; bool flag = false; int[] array = new int[4096]; byte[] array2 = new byte[4096]; byte[] array3 = new byte[4097]; byte[] array4 = new byte[expectedPixelCount]; int outputOffset = 0; for (int i = 0; i < num; i++) { array2[i] = (byte)i; } SubBlockBitReader subBlockBitReader = new SubBlockBitReader(reader); while (true) { int num6 = subBlockBitReader.ReadCode(num4); if (num6 == num) { num4 = minimumCodeSize + 1; num3 = num2 + 1; num5 = -1; flag = true; continue; } if (!flag) { throw new InvalidDataException("GIF LZW stream does not begin with a clear code."); } if (num6 == num2) { if (outputOffset != expectedPixelCount) { throw new InvalidDataException("GIF LZW stream produced an unexpected pixel count."); } subBlockBitReader.FinishAfterEndCode(); return array4; } if (num5 < 0) { if (num6 < 0 || num6 >= num) { throw new InvalidDataException("GIF LZW stream has an invalid first code after clear."); } WriteOutput(array4, ref outputOffset, (byte)num6); b = (byte)num6; num5 = num6; continue; } int num7 = num6; int num8 = 0; if (num6 == num3) { if (num3 >= 4096) { throw new InvalidDataException("GIF LZW stream references an unavailable code."); } array3[num8++] = b; num6 = num5; } else if (num6 > num3) { throw new InvalidDataException("GIF LZW stream references an undefined code."); } int num9 = 0; while (num6 >= num) { if (num6 < num2 + 1 || num6 >= num3) { throw new InvalidDataException("GIF LZW dictionary chain is invalid."); } if (num8 >= array3.Length || ++num9 > 4096) { throw new InvalidDataException("GIF LZW dictionary chain is cyclic or too deep."); } array3[num8++] = array2[num6]; num6 = array[num6]; } if (num6 < 0 || num6 >= num) { throw new InvalidDataException("GIF LZW dictionary does not end in a literal."); } b = array2[num6]; if (num8 >= array3.Length) { break; } array3[num8++] = b; while (num8 > 0) { WriteOutput(array4, ref outputOffset, array3[--num8]); } if (num3 < 4096) { array[num3] = num5; array2[num3] = b; num3++; if (num3 == 1 << num4 && num4 < 12) { num4++; } } num5 = num7; } throw new InvalidDataException("GIF LZW output stack overflowed."); } private static void WriteOutput(byte[] output, ref int outputOffset, byte value) { if (outputOffset >= output.Length) { throw new InvalidDataException("GIF LZW stream produced too many pixels."); } output[outputOffset++] = value; } } internal static class ClanEmoji { [HarmonyPatch(typeof(Terminal), "UpdateChat")] private static class TerminalUpdateChatPatch { private static void Prefix(Terminal __instance) { StopChatOutputAnimationsBeforeRewrite(__instance); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static class ChatTextEmojiPatch { [HarmonyPrefix] private static void Prefix(TMP_Text __instance, ref string value) { if ((Object)(object)__instance == (Object)null || !ClanVanillaChatDock.SupportsCurrentChatUi || (Object)(object)Chat.instance == (Object)null) { return; } TMP_Text output = (TMP_Text)(object)((Terminal)Chat.instance).m_output; if (output != null && __instance == output) { TMP_SpriteAnimator component = ((Component)__instance).GetComponent(); if (component != null) { component.StopAllAnimations(); } if (ContainsEmojiMarker(value)) { value = RenderTokens(value); int budget = (((Component)__instance).gameObject.activeInHierarchy ? 15 : 0); value = ApplyAnimatedSpriteBudget(value, budget); } } } } private enum MediaRole { Emoji, Emblem } private enum EmojiFileKind { Png, Gif } private sealed class ServerFileStamp { public readonly string FullPath; public readonly long Length; public readonly long LastWriteTimeUtcTicks; private ServerFileStamp(string fullPath, long length, long lastWriteTimeUtcTicks) { FullPath = fullPath; Length = length; LastWriteTimeUtcTicks = lastWriteTimeUtcTicks; } public static ServerFileStamp Capture(FileInfo file) { file.Refresh(); if (!file.Exists) { throw new FileNotFoundException("Media file '" + file.Name + "' disappeared while it was being scanned.", file.FullName); } return new ServerFileStamp(Path.GetFullPath(file.FullName), file.Length, file.LastWriteTimeUtc.Ticks); } public bool Matches(ServerFileStamp other) { if (FilePathComparer.Equals(FullPath, other.FullPath) && Length == other.Length) { return LastWriteTimeUtcTicks == other.LastWriteTimeUtcTicks; } return false; } } private sealed class ServerFileCandidate { public readonly ServerEmojiBlob Blob; public readonly ServerFileStamp? Stamp; public ServerFileCandidate(ServerEmojiBlob blob, ServerFileStamp? stamp) { Blob = blob; Stamp = stamp; } } private sealed class ManifestRecord { public readonly MediaRole Role; public readonly EmojiFileKind Kind; public readonly string Name; public readonly int Length; public readonly string Hash; public string Extension { get { if (Kind != EmojiFileKind.Png) { return ".gif"; } return ".png"; } } public ManifestRecord(MediaRole role, EmojiFileKind kind, string name, int length, string hash) { Role = role; Kind = kind; Name = name; Length = length; Hash = hash; } } private sealed class PendingManifestState { public readonly string Manifest; public readonly ZNet? Session; public PendingManifestState(string manifest, ZNet? session) { Manifest = manifest; Session = session; } } private enum RuntimeBuildStage { StaticEmoji, AnimatedEmoji, Emblems, Finalize, Completed } private sealed class BuildState { public readonly string Manifest; public readonly IReadOnlyList Records; public readonly ZNet? Session; public readonly List Sources = new List(); public readonly Dictionary PreparedSources; public readonly CancellationTokenSource Cancellation = new CancellationTokenSource(); public ManifestRecord? PendingGifRecord; public Task? PendingGifTask; public RuntimeBuildState? RuntimeBuild; public int NextRecordIndex; public int Failures; public int TotalGifFrames; public int GifFrameBudgetSkips; public BuildState(string manifest, IReadOnlyList records, ZNet? session) { Manifest = manifest; Records = records; Session = session; PreparedSources = new Dictionary(_preparedSources, StringComparer.Ordinal); } public void Cancel() { Cancellation.Cancel(); RuntimeBuild?.Destroy(); } } private sealed class RuntimeBuildState { public readonly string ManifestHash; public readonly IReadOnlyList EmojiSources; public readonly IReadOnlyList EmblemSources; public readonly IReadOnlyList StillSources; public readonly IReadOnlyList AnimatedSources; public readonly List Sheets = new List(); public readonly List EmojiSheets = new List(); public readonly Dictionary References = new Dictionary(); public RuntimeBuildStage Stage; public int NextAnimatedSourceIndex; private bool _ownsSheets = true; public RuntimeBuildState(string manifest, IReadOnlyList sources) { ManifestHash = ComputeSha256(Encoding.UTF8.GetBytes(manifest)).Substring(0, 12); EmojiSources = sources.Where((SourceEmoji source) => source.Record.Role == MediaRole.Emoji).ToArray(); EmblemSources = sources.Where((SourceEmoji source) => source.Record.Role == MediaRole.Emblem).ToArray(); if (EmblemSources.Any((SourceEmoji source) => source.Frames.Count != 1)) { throw new InvalidDataException("Clan emblems must contain exactly one PNG frame."); } StillSources = EmojiSources.Where((SourceEmoji source) => source.Frames.Count == 1).ToArray(); AnimatedSources = EmojiSources.Where((SourceEmoji source) => source.Frames.Count > 1).ToArray(); } public void ReleaseOwnership() { _ownsSheets = false; Sheets.Clear(); EmojiSheets.Clear(); References.Clear(); } public void Destroy() { if (!_ownsSheets) { return; } _ownsSheets = false; foreach (RuntimeSheet sheet in Sheets) { sheet.SpriteAsset.fallbackSpriteAssets?.Clear(); } foreach (RuntimeSheet sheet2 in Sheets) { DestroyRuntimeSheet(sheet2); } Sheets.Clear(); EmojiSheets.Clear(); References.Clear(); } } private sealed class SourceEmoji { public readonly ManifestRecord Record; public readonly int Width; public readonly int Height; public readonly IReadOnlyList Frames; public readonly int FramesPerSecond; public SourceEmoji(ManifestRecord record, int width, int height, IReadOnlyList frames, int framesPerSecond) { Record = record; Width = width; Height = height; Frames = frames; FramesPerSecond = framesPerSecond; } public SourceEmoji WithRecord(ManifestRecord record) { return new SourceEmoji(record, Width, Height, Frames, FramesPerSecond); } } private sealed class FrameSpec { public readonly SourceEmoji Source; public readonly int FrameIndex; public readonly string SpriteName; public byte[] Pixels => Source.Frames[FrameIndex]; public FrameSpec(SourceEmoji source, int frameIndex, string spriteName) { Source = source; FrameIndex = frameIndex; SpriteName = spriteName; } } private sealed class SourceRuntimeReference { public readonly RuntimeSheet Sheet; public readonly int FirstFrameIndex; public SourceRuntimeReference(RuntimeSheet sheet, int firstFrameIndex) { Sheet = sheet; FirstFrameIndex = firstFrameIndex; } } private sealed class RuntimeEmoji { public readonly string Token; public readonly Sprite PickerSprite; public readonly bool IsGif; public RuntimeEmoji(string token, Sprite pickerSprite, bool isGif) { Token = token; PickerSprite = pickerSprite; IsGif = isGif; } } internal readonly struct ClanEmblemPickerItem { public readonly string Name; public readonly Sprite Sprite; public ClanEmblemPickerItem(string name, Sprite sprite) { Name = name; Sprite = sprite; } } private sealed class RuntimeRenderTag { public readonly string AnimatedTag; public readonly string StaticTag; public readonly bool IsAnimated; public RuntimeRenderTag(string animatedTag, string staticTag, bool isAnimated) { AnimatedTag = animatedTag; StaticTag = staticTag; IsAnimated = isAnimated; } } private sealed class RuntimeSheet { public readonly Texture2D Texture; public readonly TMP_SpriteAsset SpriteAsset; public readonly Material Material; public readonly Sprite[] Sprites; public RuntimeSheet(Texture2D texture, TMP_SpriteAsset spriteAsset, Material material, Sprite[] sprites) { Texture = texture; SpriteAsset = spriteAsset; Material = material; Sprites = sprites; } } private sealed class RuntimeLibrary { public readonly TMP_SpriteAsset? RootSpriteAsset; public readonly IReadOnlyList Sheets; public readonly IReadOnlyList Entries; public readonly IReadOnlyList Emblems; public readonly IReadOnlyDictionary EmblemsByName; public readonly IReadOnlyDictionary RenderTags; public readonly Regex TokenRegex; public readonly IReadOnlyDictionary RenderTagsBySpriteName; public readonly Regex SpriteTagRegex; public RuntimeLibrary(TMP_SpriteAsset? rootSpriteAsset, IReadOnlyList sheets, IReadOnlyList entries, IReadOnlyList emblems, IReadOnlyDictionary emblemsByName, IReadOnlyDictionary renderTags, Regex tokenRegex, IReadOnlyDictionary renderTagsBySpriteName, Regex spriteTagRegex) { RootSpriteAsset = rootSpriteAsset; Sheets = sheets; Entries = entries; Emblems = emblems; EmblemsByName = emblemsByName; RenderTags = renderTags; TokenRegex = tokenRegex; RenderTagsBySpriteName = renderTagsBySpriteName; SpriteTagRegex = spriteTagRegex; } } private enum EmojiFileResponseStatus : byte { Data, Busy, StaleCatalog, Unavailable, Rejected } private enum SyncedMediaFileSource : byte { None, Server, Cache, Config } private sealed class ServerEmojiBlob { public readonly ManifestRecord Record; public readonly byte[] Data; public readonly int GifFrameCount; public ServerEmojiBlob(ManifestRecord record, byte[] data, int gifFrameCount) { Record = record; Data = data; GifFrameCount = gifFrameCount; } } private sealed class ServerEmojiCatalog { public readonly string Manifest; public readonly string CatalogId; public readonly IReadOnlyList Records; public readonly IReadOnlyDictionary Files; public ServerEmojiCatalog(string manifest, string catalogId, IReadOnlyList records, IReadOnlyDictionary files) { Manifest = manifest; CatalogId = catalogId; Records = records; Files = files; } } private sealed class ClientEmojiCatalog { public readonly ZNet? Session; public readonly string Manifest; public readonly string CatalogId; public readonly IReadOnlyList Records; public int NextRecordIndex; public int ServerHits; public int CacheHits; public int ConfigHits; public int Downloads; public ClientEmojiDownload? Download; public bool Ready; public bool Failed; public int CatalogRetryCount; public float CatalogRetryAt; public ClientEmojiCatalog(ZNet? session, string manifest, string catalogId, IReadOnlyList records) { Session = session; Manifest = manifest; CatalogId = catalogId; Records = records; } } private sealed class ClientEmojiDownload { private const byte Missing = 0; private const byte InFlight = 1; private const byte Received = 2; public readonly ManifestRecord Record; public readonly byte[] Buffer; private readonly byte[] _chunkStates; private readonly int[] _requestIds; private readonly int[] _retryCounts; private readonly float[] _nextRequestAt; private readonly float[] _responseDeadlines; private int _nextChunkCursor; private int _receivedBytes; public int ChunkCount => _chunkStates.Length; public int InFlightCount { get; private set; } public bool Completed => _receivedBytes == Buffer.Length; public ClientEmojiDownload(ManifestRecord record) { Record = record; Buffer = new byte[record.Length]; int num = (record.Length + 65536 - 1) / 65536; _chunkStates = new byte[num]; _requestIds = new int[num]; _retryCounts = new int[num]; _nextRequestAt = new float[num]; _responseDeadlines = new float[num]; } public bool TryGetChunkIndex(int offset, out int chunkIndex) { chunkIndex = offset / 65536; if (offset >= 0 && offset % 65536 == 0 && offset < Buffer.Length && chunkIndex >= 0) { return chunkIndex < ChunkCount; } return false; } public bool HasIssuedRequest(int chunkIndex, int requestId) { if (requestId > 0) { return requestId <= _requestIds[chunkIndex]; } return false; } public bool IsCurrentRequest(int chunkIndex, int requestId) { if (_chunkStates[chunkIndex] == 1) { return _requestIds[chunkIndex] == requestId; } return false; } public bool IsChunkReceived(int chunkIndex) { return _chunkStates[chunkIndex] == 2; } public bool IsResponseTimedOut(int chunkIndex, float now) { if (_chunkStates[chunkIndex] == 1) { return now >= _responseDeadlines[chunkIndex]; } return false; } public bool TryGetNextRequestChunk(float now, out int chunkIndex) { for (int i = 0; i < ChunkCount; i++) { int num = (_nextChunkCursor + i) % ChunkCount; if (_chunkStates[num] == 0 && !(now < _nextRequestAt[num])) { _nextChunkCursor = (num + 1) % ChunkCount; chunkIndex = num; return true; } } chunkIndex = -1; return false; } public int MarkRequested(int chunkIndex, float responseDeadline) { if (_chunkStates[chunkIndex] != 0) { throw new InvalidOperationException("Media chunk is already in flight."); } int result = ++_requestIds[chunkIndex]; _chunkStates[chunkIndex] = 1; _responseDeadlines[chunkIndex] = responseDeadline; InFlightCount++; return result; } public void MarkReceived(int chunkIndex, int length) { if (_chunkStates[chunkIndex] != 2) { if (_chunkStates[chunkIndex] == 1) { InFlightCount--; } _chunkStates[chunkIndex] = 2; _retryCounts[chunkIndex] = 0; _receivedBytes += length; if (_receivedBytes > Buffer.Length) { throw new InvalidDataException("Received media chunks exceed the manifest length."); } } } public int IncrementRetry(int chunkIndex) { return ++_retryCounts[chunkIndex]; } public void DeferChunk(int chunkIndex, float nextRequestAt) { if (_chunkStates[chunkIndex] != 2) { if (_chunkStates[chunkIndex] == 1) { InFlightCount--; } _chunkStates[chunkIndex] = 0; _nextRequestAt[chunkIndex] = Math.Max(_nextRequestAt[chunkIndex], nextRequestAt); } } } private sealed class PeerTransferBudget { private float _lastRefillAt; private float _malformedWindowStartedAt; private double _requestTokens; public double ByteTokens; public int MalformedLogs; public PeerTransferBudget(float now) { _lastRefillAt = now; _malformedWindowStartedAt = now; _requestTokens = 32.0; ByteTokens = 262144.0; } public void Refill(float now) { float num = Math.Max(0f, now - _lastRefillAt); _lastRefillAt = now; _requestTokens = Math.Min(32.0, _requestTokens + (double)(num * 64f)); ByteTokens = Math.Min(262144.0, ByteTokens + (double)(num * (2635359f / (float)Math.PI))); } public bool TryConsumeRequest(float now) { Refill(now); if (_requestTokens < 1.0) { return false; } _requestTokens -= 1.0; return true; } public bool TryConsumeMalformedLog(float now) { if (now < _malformedWindowStartedAt || now - _malformedWindowStartedAt >= 10f) { _malformedWindowStartedAt = now; MalformedLogs = 0; } if (MalformedLogs >= 3) { return false; } MalformedLogs++; return true; } } private sealed class GlobalTransferBudget { private float _lastRefillAt = -1f; public double ByteTokens { get; set; } public GlobalTransferBudget() { Reset(); } public void Reset() { _lastRefillAt = -1f; ByteTokens = 1048576.0; } public void Refill(float now) { if (_lastRefillAt < 0f || now < _lastRefillAt) { _lastRefillAt = now; return; } float num = now - _lastRefillAt; _lastRefillAt = now; ByteTokens = Math.Min(1048576.0, ByteTokens + (double)(num * (10541436f / (float)Math.PI))); } } private const int MaximumEmojiFiles = 100; private const int MaximumPngFiles = 100; private const int MaximumGifFiles = 50; private const int MaximumEmblemFiles = 50; private const int MaximumPngSourceBytes = 524288; private const int MaximumGifSourceBytes = 2097152; private const int MaximumSourceDimension = 512; private const int MaximumGifFrames = 180; private const int MaximumTotalGifFrames = 3000; private const long MaximumGifDecodedPixels = 8388608L; private const long MaximumGifDurationMilliseconds = 10000L; private const int MaximumRenderedDimension = 96; private const int MaximumGifPlaybackFps = 50; private const int MaximumEmojiOccurrencesPerMessage = 5; private const int MaximumAnimatedOccurrencesPerChatPanel = 15; private const int EmojiSizePercent = 200; private const int AtlasGutter = 2; private const int AtlasColumns = 5; private const long MediaCacheRetryDelayTicks = 10000000L; private const int MaximumManifestCharacters = 24576; private const string ManifestVersion = "v4"; private const string SpriteAssetVersion = "1.1.0"; private const string TokenPrefix = ":clan_"; private const string SpriteNamePrefix = "clan_emoji_"; private const string PngSpriteNamePrefix = "clan_emoji_png_"; private const string GifSpriteNamePrefix = "clan_emoji_gif_"; private const string EmblemSpriteNamePrefix = "clan_emblem_"; private static readonly byte[] PngSignature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 }; private static readonly Regex SafeNameRegex = new Regex("^[a-z0-9](?:[a-z0-9_-]{0,30}[a-z0-9])?$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly HashSet ReservedWindowsNames = BuildReservedWindowsNames(); private static readonly StringComparer FilePathComparer = ((Environment.OSVersion.Platform == PlatformID.Win32NT) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); private static readonly object ServerMediaDirtyLock = new object(); private static readonly SemaphoreSlim GifDecodeGate = new SemaphoreSlim(1, 1); private static readonly Dictionary DirtyServerMediaPaths = new Dictionary(FilePathComparer); private static Dictionary _serverFileStamps = new Dictionary(StringComparer.Ordinal); private static long _serverMediaDirtyGeneration; private static readonly FieldInfo? SpriteAssetVersionField = typeof(TMP_Asset).GetField("m_Version", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly ConfigSync MediaConfigSync = new ConfigSync("sighsorry.Clan.media") { DisplayName = "Clan Media", CurrentVersion = "1.0.1", MinimumRequiredVersion = "1.0.1", ModRequired = false, IsLocked = true }; private static readonly CustomSyncedValue SyncedManifest = new CustomSyncedValue(MediaConfigSync, "manifest", ""); private static bool _initialized; private static bool _mediaCacheInitialized; private static bool _mediaCacheWarningLogged; private static long _mediaCacheRetryAtTicks; private static bool _serverManifestPublishRetryPending; private static ZNet? _session; private static RuntimeLibrary? _runtime; private static BuildState? _build; private static Dictionary _preparedSources = new Dictionary(StringComparer.Ordinal); private static string _activeManifest = ""; private static IReadOnlyList _activeRecords = Array.Empty(); private static string _waitingManifest = ""; private static List? _waitingRecords; private static PendingManifestState? _pendingManifest; private static TMP_Text? _attachedOutput; private static TMP_SpriteAsset? _fallbackOwner; private static bool _installedAsPrimary; private static bool _addedFallback; private const int EmojiFileProtocolVersion = 3; private const int EmojiChunkBytes = 65536; private const int MaximumParallelEmojiChunks = 4; private const int MaximumEmojiRequestBytes = 512; private const int MaximumEmojiResponseBytes = 66048; private const int MaximumChunkRetries = 3; private const float ChunkResponseTimeoutSeconds = 5f; private const long EmojiCacheMaximumBytes = 402653184L; private const long EmojiCacheTrimmedBytes = 335544320L; private const long PartialEmojiCacheMaximumAgeTicks = 864000000000L; private const long EmojiReloadDebounceTicks = 7500000L; private const int MaximumReloadRetries = 3; private const float PeerRequestTokensPerSecond = 64f; private const float PeerRequestTokenCapacity = 32f; private const float PeerTransferBytesPerSecond = 2635359f / (float)Math.PI; private const float PeerTransferByteCapacity = 262144f; private const float GlobalTransferBytesPerSecond = 10541436f / (float)Math.PI; private const float GlobalTransferByteCapacity = 1048576f; private const int MaximumMediaSendQueueBytes = 262144; private const float MalformedLogWindowSeconds = 10f; private static readonly string EmojiFileRequestRpc = string.Format("{0}.media.file.request.v{1}", "sighsorry.Clan", 3); private static readonly string EmojiFileResponseRpc = string.Format("{0}.media.file.response.v{1}", "sighsorry.Clan", 3); private static readonly Dictionary EmojiPeerBudgets = new Dictionary(); private static readonly GlobalTransferBudget EmojiGlobalTransferBudget = new GlobalTransferBudget(); private static ServerEmojiCatalog? _serverEmojiCatalog; private static ClientEmojiCatalog? _clientEmojiCatalog; private static FileSystemWatcher? _emojiFileWatcher; private static FileSystemWatcher? _emblemFileWatcher; private static long _emojiReloadRequestedAtTicks; private static int _emojiReloadFailures; private static int _emojiWatcherNeedsRestart; private static int _emojiWatcherRestartFailures; public static bool IsReady { get { if (_runtime != null) { return _runtime.Entries.Count > 0; } return false; } } public static int EmojiCount => _runtime?.Entries.Count ?? 0; internal static int MessageEmojiLimit => 5; private static string EmojiDirectory => Path.Combine(ClanPlugin.MediaDirectory, "emoji"); private static string EmblemDirectory => Path.Combine(ClanPlugin.MediaDirectory, "emblems"); private static string EmojiCacheDirectory => Path.Combine(ClanPlugin.DataDirectory, "cache"); private static bool HasEmojiServerCatalog => _serverEmojiCatalog != null; public static event Action? EmojiChanged; public static event Action? EmblemsChanged; internal static IReadOnlyList GetEmblemPickerItems() { if (_runtime == null) { return Array.Empty(); } return _runtime.Emblems; } internal static Sprite? GetEmblemSprite(string? name) { if (_runtime == null || string.IsNullOrWhiteSpace(name)) { return null; } if (!_runtime.EmblemsByName.TryGetValue(name, out var value)) { return null; } return value.Sprite; } internal static bool IsAvailableEmblemKey(string? name) { if (string.IsNullOrWhiteSpace(name)) { return true; } if (!IsSafeName(name)) { return false; } if (_serverEmojiCatalog != null) { return _serverEmojiCatalog.Records.Any((ManifestRecord record) => record.Role == MediaRole.Emblem && StringComparer.Ordinal.Equals(record.Name, name)); } if (_runtime != null) { return _runtime.EmblemsByName.ContainsKey(name); } return false; } internal static void MarkEmojiServerFileDirty(string path) { try { string fullPath = Path.GetFullPath(path); lock (ServerMediaDirtyLock) { DirtyServerMediaPaths[fullPath] = ++_serverMediaDirtyGeneration; } } catch (Exception ex) when (((ex is ArgumentException || ex is NotSupportedException || ex is PathTooLongException) ? 1 : 0) != 0) { } MarkEmojiServerFilesDirty(); } private static Dictionary SnapshotDirtyServerMediaPaths() { lock (ServerMediaDirtyLock) { return new Dictionary(DirtyServerMediaPaths, FilePathComparer); } } private static bool IsServerMediaPathDirty(string path, IReadOnlyDictionary dirtyPaths) { return dirtyPaths.ContainsKey(Path.GetFullPath(path)); } private static void AcknowledgeDirtyServerMediaPaths(IReadOnlyDictionary dirtyPaths, IEnumerable retryablePaths) { HashSet hashSet = retryablePaths.Select(Path.GetFullPath).ToHashSet(FilePathComparer); lock (ServerMediaDirtyLock) { foreach (KeyValuePair dirtyPath in dirtyPaths) { if (!hashSet.Contains(dirtyPath.Key) && DirtyServerMediaPaths.TryGetValue(dirtyPath.Key, out var value) && value == dirtyPath.Value) { DirtyServerMediaPaths.Remove(dirtyPath.Key); } } } } public static void Init() { if (!_initialized) { _initialized = true; SyncedManifest.ValueChanged += OnSyncedManifestChanged; } } private static bool EnsureMediaCacheInitialized() { if (_mediaCacheInitialized) { return true; } long ticks = DateTime.UtcNow.Ticks; if (ticks < _mediaCacheRetryAtTicks) { return false; } _mediaCacheRetryAtTicks = ticks + 10000000; try { InitializeEmojiCache(); _mediaCacheInitialized = true; _mediaCacheRetryAtTicks = 0L; if (_mediaCacheWarningLogged) { ClanPlugin.ClanLogger.LogInfo((object)"Clan media cache initialization recovered."); _mediaCacheWarningLogged = false; } return true; } catch (Exception ex) { if (!_mediaCacheWarningLogged) { ClanPlugin.ClanLogger.LogWarning((object)("Clan media cache is not ready and will be retried: " + ex.Message)); _mediaCacheWarningLogged = true; } return false; } } public static void Dispose() { if (_initialized) { SyncedManifest.ValueChanged -= OnSyncedManifestChanged; _session = null; _pendingManifest = null; _mediaCacheInitialized = false; _mediaCacheWarningLogged = false; _mediaCacheRetryAtTicks = 0L; ResetMediaSessionState(null); _initialized = false; } } private static void ResetMediaSessionState(ZNet? session) { CancelBuild(); _preparedSources.Clear(); _activeManifest = ""; _activeRecords = Array.Empty(); _waitingManifest = ""; _waitingRecords = null; ResetEmojiSyncSession(session); ReplaceRuntime(null); } public static void Tick() { if (!_initialized) { return; } ZNet instance = ZNet.instance; if (instance != _session) { _session = instance; if (_pendingManifest != null && _pendingManifest.Session != instance) { _pendingManifest = null; } ResetMediaSessionState(instance); if (instance != null && instance.IsServer()) { LoadServerManifest(preserveLastGood: false); } } if ((Object)(object)instance != (Object)null && !instance.IsServer() && !EnsureMediaCacheInitialized()) { return; } if (instance != null && instance.IsServer() && ConsumeEmojiServerReloadRequest()) { LoadServerManifest(preserveLastGood: true); } if (GUIManager.IsHeadless()) { return; } PendingManifestState pendingManifest = _pendingManifest; if (pendingManifest != null) { _pendingManifest = null; if (pendingManifest.Session == ZNet.instance) { StartBuild(pendingManifest.Manifest); } } TickEmojiSync(); StartReadyEmojiBuild(); ProcessBuildStep(); EnsureOutputAttachment(); } public static Sprite? GetPickerSprite(int index) { if (_runtime == null || index < 0 || index >= _runtime.Entries.Count) { return null; } return _runtime.Entries[index].PickerSprite; } internal static bool IsGifEmoji(int index) { if (_runtime != null && index >= 0 && index < _runtime.Entries.Count) { return _runtime.Entries[index].IsGif; } return false; } public static string TokenFor(int index) { if (_runtime == null || index < 0 || index >= _runtime.Entries.Count) { return ""; } return _runtime.Entries[index].Token; } internal static int CountMessageEmojiTokens(string? text) { if (_runtime == null || string.IsNullOrEmpty(text)) { return 0; } return _runtime.TokenRegex.Matches(text).Count; } internal static bool IsWithinMessageEmojiLimit(string? text) { return CountMessageEmojiTokens(text) <= 5; } public static string RenderTokens(string text) { if (_runtime == null || string.IsNullOrEmpty(text) || text.IndexOf(":clan_", StringComparison.OrdinalIgnoreCase) < 0) { return text; } int emojiOccurrences = 0; int previousMatchEnd = 0; return _runtime.TokenRegex.Replace(text, delegate(Match match) { if (ContainsLineBreak(text, previousMatchEnd, match.Index)) { emojiOccurrences = 0; } previousMatchEnd = match.Index + match.Length; if (!_runtime.RenderTags.TryGetValue(match.Value, out RuntimeRenderTag value)) { return match.Value; } emojiOccurrences++; if (emojiOccurrences > 5) { return EscapeTokenForDisplay(match.Value); } string arg = (value.IsAnimated ? value.AnimatedTag : value.StaticTag); return $"{arg}"; }); } private static bool ContainsLineBreak(string text, int startIndex, int endIndex) { for (int i = startIndex; i < endIndex; i++) { char c = text[i]; if ((c == '\n' || c == '\r') ? true : false) { return true; } } return false; } private static string EscapeTokenForDisplay(string token) { if (token.Length != 0) { return token.Insert(1, "\u200b"); } return token; } private static int CountAnimatedSpriteOccurrences(string text) { RuntimeLibrary runtime = _runtime; if (runtime == null || string.IsNullOrEmpty(text)) { return 0; } int num = 0; foreach (Match item in runtime.SpriteTagRegex.Matches(text)) { if (runtime.RenderTagsBySpriteName.TryGetValue(item.Groups["name"].Value, out RuntimeRenderTag value) && value.IsAnimated) { num++; } } return num; } private static string RewriteAnimatedSpriteTags(string text, ref int animatedOrdinal, int animatedOccurrencesToFreeze) { RuntimeLibrary runtime = _runtime; if (runtime == null || string.IsNullOrEmpty(text)) { return text; } int ordinal = animatedOrdinal; string result = runtime.SpriteTagRegex.Replace(text, delegate(Match match) { if (!runtime.RenderTagsBySpriteName.TryGetValue(match.Groups["name"].Value, out RuntimeRenderTag value)) { return match.Value; } if (!value.IsAnimated) { return value.StaticTag; } return (ordinal++ >= animatedOccurrencesToFreeze) ? value.AnimatedTag : value.StaticTag; }); animatedOrdinal = ordinal; return result; } private static string ApplyAnimatedSpriteBudget(string text, int budget) { int num = CountAnimatedSpriteOccurrences(text); int animatedOccurrencesToFreeze = Math.Max(0, num - budget); int animatedOrdinal = 0; return RewriteAnimatedSpriteTags(text, ref animatedOrdinal, animatedOccurrencesToFreeze); } private static void LoadServerManifest(bool preserveLastGood) { Dictionary dirtyPaths; Dictionary dictionary; List list; List list2; List list3; List list4; string text; bool flag; bool serverManifestPublishRetryPending; ServerEmojiCatalog serverEmojiCatalog; try { Directory.CreateDirectory(EmojiDirectory); Directory.CreateDirectory(EmblemDirectory); dirtyPaths = SnapshotDirtyServerMediaPaths(); List blobs = new List(); dictionary = new Dictionary(StringComparer.Ordinal); list = new List(); list2 = new List(); list3 = new List(); list4 = DiscoverServerFiles(preserveLastGood, dirtyPaths, blobs, dictionary, list, list2, list3); text = SerializeManifest(list4); flag = !IsCurrentEmojiServerManifest(text); serverEmojiCatalog = (flag ? PrepareEmojiServerCatalog(text, list4, blobs) : null); serverManifestPublishRetryPending = _serverManifestPublishRetryPending; if (!preserveLastGood || serverManifestPublishRetryPending || !StringComparer.Ordinal.Equals(SyncedManifest.Value, text)) { try { SyncedManifest.Value = text; _serverManifestPublishRetryPending = false; } catch (Exception ex) { _serverManifestPublishRetryPending = true; bool flag2 = StringComparer.Ordinal.Equals(SyncedManifest.Value, text); if (flag2 && serverEmojiCatalog != null) { CommitEmojiServerCatalog(serverEmojiCatalog); serverEmojiCatalog = null; } ScheduleEmojiServerReloadRetry(ex, retryAnyError: true); if (flag2 && !GUIManager.IsHeadless()) { QueueManifest(text); } ClanPlugin.ClanLogger.LogWarning((object)(flag2 ? ("Clan media manifest was assigned, but its synchronization failed and will be retried: " + ex.Message) : ("Clan media manifest synchronization failed; the last valid catalog remains active and publication will be retried: " + ex.Message))); return; } } } catch (Exception ex2) { if (preserveLastGood && HasEmojiServerCatalog) { ScheduleEmojiServerReloadRetry(ex2); ClanPlugin.ClanLogger.LogWarning((object)("Clan media hot reload was rejected; the last valid catalog remains active: " + ex2.Message)); return; } if (_serverManifestPublishRetryPending || !StringComparer.Ordinal.Equals(SyncedManifest.Value, "")) { try { SyncedManifest.Value = ""; _serverManifestPublishRetryPending = false; } catch (Exception ex3) { _serverManifestPublishRetryPending = true; bool flag3 = StringComparer.Ordinal.Equals(SyncedManifest.Value, ""); ScheduleEmojiServerReloadRetry(ex3, retryAnyError: true); if (flag3) { ClearEmojiServerCatalog(); if (!GUIManager.IsHeadless()) { QueueManifest(""); } } ClanPlugin.ClanLogger.LogWarning((object)(flag3 ? ("Clan media was disabled after catalog loading failed, but empty-manifest synchronization also failed and remains pending: " + ex3.Message) : ("Clan media catalog loading failed and the previous manifest could not be replaced; publication remains pending: " + ex3.Message))); return; } } ClearEmojiServerCatalog(); if (!GUIManager.IsHeadless()) { QueueManifest(""); } ClanPlugin.ClanLogger.LogError((object)("Clan media manifest was disabled: " + ex2.Message)); return; } if (serverEmojiCatalog != null) { CommitEmojiServerCatalog(serverEmojiCatalog); } if ((flag || serverManifestPublishRetryPending) && !GUIManager.IsHeadless()) { QueueManifest(text); } try { AcknowledgeDirtyServerMediaPaths(dirtyPaths, list3); _serverFileStamps = dictionary; if (list2.Count == 0) { ResetEmojiServerReloadFailures(); } else { ScheduleEmojiServerReloadRetry(list2[0]); } foreach (string item in list) { ClanPlugin.ClanLogger.LogWarning((object)item); } if (!flag) { return; } if (list4.Count == 0) { if (list.Count == 0) { ClanPlugin.ClanLogger.LogInfo((object)($"Clan media folders are empty. Put up to {100} total PNG/GIF " + $"emoji files (up to {100} PNG or {50} GIF) " + "in '" + EmojiDirectory + "', and up to " + $"{50} PNG emblem files in '{EmblemDirectory}'.")); } else { ClanPlugin.ClanLogger.LogWarning((object)"No valid Clan media files are available; invalid files were skipped."); } } else { int num = list4.Count((ManifestRecord record) => record.Role == MediaRole.Emoji && record.Kind == EmojiFileKind.Png); int num2 = list4.Count((ManifestRecord record) => record.Role == MediaRole.Emoji && record.Kind == EmojiFileKind.Gif); int num3 = list4.Count((ManifestRecord record) => record.Role == MediaRole.Emblem); ClanPlugin.ClanLogger.LogInfo((object)($"Published Clan media catalog for {num} PNG emoji, {num2} GIF emoji, " + $"and {num3} PNG emblem files. " + "Clients download only content missing from verified local media and their SHA-256 cache.")); } } catch (Exception ex4) { ClanPlugin.ClanLogger.LogWarning((object)("Clan media catalog is active, but post-publish maintenance could not be completed: " + ex4.Message)); } } private static List DiscoverServerFiles(bool preserveLastGood, IReadOnlyDictionary dirtyPaths, ICollection blobs, IDictionary fileStamps, ICollection warnings, ICollection retryableFileErrors, ICollection retryableFilePaths) { List list = new List(); Dictionary previousServerFiles = GetPreviousServerFiles(preserveLastGood); DiscoverServerFiles(MediaRole.Emoji, EmojiDirectory, 100, 50, 100, dirtyPaths, previousServerFiles, blobs, fileStamps, list, warnings, retryableFileErrors, retryableFilePaths); DiscoverServerFiles(MediaRole.Emblem, EmblemDirectory, 50, 0, 50, dirtyPaths, previousServerFiles, blobs, fileStamps, list, warnings, retryableFileErrors, retryableFilePaths); return list; } private static Dictionary GetPreviousServerFiles(bool preserveLastGood) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (!preserveLastGood || _serverEmojiCatalog == null) { return dictionary; } foreach (ManifestRecord record in _serverEmojiCatalog.Records) { if (_serverEmojiCatalog.Files.TryGetValue(record.Hash, out ServerEmojiBlob value) && value.Data.Length == record.Length) { string key = MediaKey(record.Role, record.Name); _serverFileStamps.TryGetValue(key, out ServerFileStamp value2); dictionary[key] = new ServerFileCandidate(new ServerEmojiBlob(record, value.Data, value.GifFrameCount), value2); } } return dictionary; } private static void DiscoverServerFiles(MediaRole role, string sourceDirectory, int maximumPngFiles, int maximumGifFiles, int maximumTotalFiles, IReadOnlyDictionary dirtyPaths, IReadOnlyDictionary previousByKey, ICollection blobs, IDictionary fileStamps, ICollection records, ICollection warnings, ICollection retryableFileErrors, ICollection retryableFilePaths) { List list = (from file in new DirectoryInfo(sourceDirectory).EnumerateFiles("*", SearchOption.TopDirectoryOnly) where StringComparer.Ordinal.Equals(file.Extension, ".png") || StringComparer.Ordinal.Equals(file.Extension, ".gif") select file).OrderBy((FileInfo file) => file.Name, StringComparer.Ordinal).ToList(); string text = ((role == MediaRole.Emoji) ? "emoji" : "emblem"); List<(FileInfo, string, EmojiFileKind)> list2 = new List<(FileInfo, string, EmojiFileKind)>(); foreach (FileInfo item2 in list) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item2.Name); if (!IsSafeName(fileNameWithoutExtension)) { warnings.Add("Skipped " + text + " file '" + item2.Name + "': filename must use lowercase ASCII letters, digits, '_' or '-' only."); } else { EmojiFileKind item = ((!StringComparer.Ordinal.Equals(item2.Extension, ".png")) ? EmojiFileKind.Gif : EmojiFileKind.Png); list2.Add((item2, fileNameWithoutExtension, item)); } } List list3 = new List(); foreach (IGrouping item3 in list2.GroupBy<(FileInfo, string, EmojiFileKind), string>(((FileInfo File, string Name, EmojiFileKind Kind) candidate) => candidate.Name, StringComparer.Ordinal).OrderBy, string>((IGrouping group) => group.Key, StringComparer.Ordinal)) { (FileInfo, string, EmojiFileKind)[] array = item3.ToArray(); if (array.Length != 1) { RetainPreviousServerFile(role, item3.Key, previousByKey, list3, warnings, text + " basename '" + item3.Key + "' is used by more than one file"); continue; } var (fileInfo, name, emojiFileKind) = array[0]; try { if (emojiFileKind == EmojiFileKind.Gif && maximumGifFiles == 0) { throw new InvalidDataException("Clan emblems must be PNG files."); } ServerFileStamp serverFileStamp = ServerFileStamp.Capture(fileInfo); int maximumSourceBytes = GetMaximumSourceBytes(emojiFileKind); if (serverFileStamp.Length <= 0 || serverFileStamp.Length > maximumSourceBytes) { throw new InvalidDataException($"file size must be between 1 and {maximumSourceBytes} bytes"); } string key = MediaKey(role, name); if (!IsServerMediaPathDirty(fileInfo.FullName, dirtyPaths) && previousByKey.TryGetValue(key, out ServerFileCandidate value) && value.Blob.Record.Kind == emojiFileKind && value.Stamp != null && value.Stamp.Matches(serverFileStamp)) { list3.Add(value); continue; } byte[] array2 = ReadStableFile(fileInfo.FullName, serverFileStamp.Length); int gifFrameCount = 0; if (emojiFileKind == EmojiFileKind.Png) { ValidatePngHeader(array2, out var _, out var _); } else { ClanGifDecoder.Animation animation = ClanGifDecoder.Decode(array2, 2097152, 512, 180, 8388608L); ValidateGifAnimation(animation); gifFrameCount = animation.Frames.Count; } fileInfo.Refresh(); ServerFileStamp serverFileStamp2 = ServerFileStamp.Capture(fileInfo); if (!serverFileStamp.Matches(serverFileStamp2)) { throw new IOException("Media file '" + fileInfo.Name + "' changed while it was being validated."); } ManifestRecord record = new ManifestRecord(role, emojiFileKind, name, array2.Length, ComputeSha256(array2)); list3.Add(new ServerFileCandidate(new ServerEmojiBlob(record, array2, gifFrameCount), serverFileStamp2)); } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is InvalidDataException) ? 1 : 0) != 0) { if ((ex is IOException || ex is InvalidDataException) ? true : false) { retryableFileErrors.Add(ex); retryableFilePaths.Add(fileInfo.FullName); } RetainPreviousServerFile(role, name, previousByKey, list3, warnings, "Rejected " + text + " file '" + fileInfo.Name + "': " + ex.Message); } } AddServerFilesWithinLimits(role, text, maximumPngFiles, maximumGifFiles, maximumTotalFiles, previousByKey, list3, blobs, fileStamps, records, warnings); } private static void RetainPreviousServerFile(MediaRole role, string name, IReadOnlyDictionary previousByKey, ICollection discovered, ICollection warnings, string failure) { if (previousByKey.TryGetValue(MediaKey(role, name), out ServerFileCandidate value)) { discovered.Add(value); warnings.Add(failure + "; keeping the previous valid version."); } else { warnings.Add(failure + "; the file was skipped."); } } private static void AddServerFilesWithinLimits(MediaRole role, string label, int maximumPngFiles, int maximumGifFiles, int maximumTotalFiles, IReadOnlyDictionary previousByKey, IEnumerable discovered, ICollection blobs, IDictionary fileStamps, ICollection records, ICollection warnings) { List list = new List(); List list2 = new List(); foreach (ServerFileCandidate item in discovered) { if (!previousByKey.TryGetValue(MediaKey(role, item.Blob.Record.Name), out ServerFileCandidate _)) { list2.Add(item); } else { list.Add(item); } } int pngCount = 0; int gifCount = 0; int gifFrameCount = 0; Dictionary accepted = new Dictionary(StringComparer.Ordinal); foreach (ServerFileCandidate item2 in list.OrderBy((ServerFileCandidate item) => item.Blob.Record.Name, StringComparer.Ordinal)) { ServerFileCandidate candidate = previousByKey[MediaKey(role, item2.Blob.Record.Name)]; TryAccept(candidate); } foreach (ServerFileCandidate item3 in list.OrderBy(delegate(ServerFileCandidate item) { ServerFileCandidate candidate2 = previousByKey[MediaKey(role, item.Blob.Record.Name)]; return GifSlotCost(item) - GifSlotCost(candidate2); }).ThenBy(delegate(ServerFileCandidate item) { ServerFileCandidate candidate2 = previousByKey[MediaKey(role, item.Blob.Record.Name)]; return GifFrameCost(item) - GifFrameCost(candidate2); }).ThenBy((ServerFileCandidate item) => item.Blob.Record.Name, StringComparer.Ordinal)) { ServerEmojiBlob blob = item3.Blob; ServerFileCandidate serverFileCandidate = previousByKey[MediaKey(role, blob.Record.Name)]; if (IsSameContent(item3, serverFileCandidate) && accepted.ContainsKey(blob.Record.Name)) { accepted[blob.Record.Name] = item3; continue; } if (accepted.TryGetValue(blob.Record.Name, out ServerFileCandidate value2)) { RemoveAccepted(value2); } if (!TryAccept(item3)) { string text = DescribeLimit(item3); if (TryAccept(serverFileCandidate)) { warnings.Add("Rejected " + label + " update for '" + blob.Record.Name + blob.Record.Extension + "': " + text + "; keeping the previous valid version."); } else { warnings.Add("Skipped " + label + " file '" + blob.Record.Name + blob.Record.Extension + "': " + text + "."); } } } foreach (ServerFileCandidate item4 in list2.OrderBy((ServerFileCandidate item) => item.Blob.Record.Name, StringComparer.Ordinal)) { if (!TryAccept(item4)) { ServerEmojiBlob blob2 = item4.Blob; warnings.Add("Skipped " + label + " file '" + blob2.Record.Name + blob2.Record.Extension + "': " + DescribeLimit(item4) + "."); } } foreach (ServerFileCandidate item5 in accepted.Values.OrderBy((ServerFileCandidate item) => item.Blob.Record.Name, StringComparer.Ordinal)) { ServerEmojiBlob blob3 = item5.Blob; records.Add(blob3.Record); blobs.Add(blob3); if (item5.Stamp != null) { fileStamps[MediaKey(role, blob3.Record.Name)] = item5.Stamp; } } string DescribeLimit(ServerFileCandidate serverFileCandidate2) { ServerEmojiBlob blob4 = serverFileCandidate2.Blob; if (accepted.Count >= maximumTotalFiles) { return $"at most {maximumTotalFiles} total {label} files are supported"; } if (blob4.Record.Kind == EmojiFileKind.Png) { if (pngCount < maximumPngFiles) { return "the file could not be admitted to the resolved catalog"; } return $"at most {maximumPngFiles} PNG files are supported"; } if (gifCount >= maximumGifFiles) { return $"at most {maximumGifFiles} GIF files are supported"; } if (blob4.GifFrameCount <= 0) { return "the validated GIF frame count is unavailable"; } if (blob4.GifFrameCount > 3000 - gifFrameCount) { return $"the catalog would exceed its {3000} total GIF frame budget"; } return "the file could not be admitted to the resolved catalog"; } static int GifFrameCost(ServerFileCandidate serverFileCandidate2) { if (serverFileCandidate2.Blob.Record.Kind != EmojiFileKind.Gif) { return 0; } return serverFileCandidate2.Blob.GifFrameCount; } static int GifSlotCost(ServerFileCandidate serverFileCandidate2) { return (serverFileCandidate2.Blob.Record.Kind == EmojiFileKind.Gif) ? 1 : 0; } static bool IsSameContent(ServerFileCandidate first, ServerFileCandidate second) { if (first.Blob.Record.Kind == second.Blob.Record.Kind && first.Blob.Record.Length == second.Blob.Record.Length && StringComparer.Ordinal.Equals(first.Blob.Record.Hash, second.Blob.Record.Hash)) { return first.Blob.GifFrameCount == second.Blob.GifFrameCount; } return false; } void RemoveAccepted(ServerFileCandidate serverFileCandidate2) { ServerEmojiBlob blob4 = serverFileCandidate2.Blob; if (!accepted.Remove(blob4.Record.Name)) { throw new InvalidOperationException("Clan " + label + " selection lost '" + blob4.Record.Name + "'."); } if (blob4.Record.Kind == EmojiFileKind.Png) { pngCount--; } else { gifCount--; gifFrameCount -= blob4.GifFrameCount; } } bool TryAccept(ServerFileCandidate serverFileCandidate2) { if (accepted.Count >= maximumTotalFiles) { return false; } ServerEmojiBlob blob4 = serverFileCandidate2.Blob; if (blob4.Record.Kind == EmojiFileKind.Png) { if (pngCount >= maximumPngFiles) { return false; } pngCount++; } else { int gifFrameCount2 = blob4.GifFrameCount; if (gifCount >= maximumGifFiles || gifFrameCount2 <= 0 || gifFrameCount2 > 3000 - gifFrameCount) { return false; } gifCount++; gifFrameCount += gifFrameCount2; } accepted.Add(blob4.Record.Name, serverFileCandidate2); return true; } } private static int GetMaximumSourceBytes(EmojiFileKind kind) { if (kind != EmojiFileKind.Gif) { return 524288; } return 2097152; } private static string MediaKey(MediaRole role, string name) { int num = (int)role; return num.ToString(CultureInfo.InvariantCulture) + "|" + name; } private static byte[] ReadStableFile(string path, long expectedLength) { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); if (fileStream.Length != expectedLength) { throw new IOException("Media file '" + Path.GetFileName(path) + "' changed while it was being scanned."); } byte[] array = new byte[fileStream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = fileStream.Read(array, i, array.Length - i); if (num <= 0) { throw new EndOfStreamException("Media file '" + Path.GetFileName(path) + "' ended before its declared length."); } } if (fileStream.Length != expectedLength) { throw new IOException("Media file '" + Path.GetFileName(path) + "' changed while it was being scanned."); } return array; } private static string SerializeManifest(IReadOnlyList records) { if (records.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder("v4"); foreach (ManifestRecord item in records.OrderBy((ManifestRecord record) => record.Role).ThenBy((ManifestRecord record) => record.Name, StringComparer.Ordinal)) { StringBuilder stringBuilder2 = stringBuilder.Append('\n').Append((item.Role == MediaRole.Emoji) ? 'e' : 'm').Append('|') .Append((item.Kind == EmojiFileKind.Png) ? 'p' : 'g') .Append('|') .Append(item.Name) .Append('|'); int length = item.Length; stringBuilder2.Append(length.ToString(CultureInfo.InvariantCulture)).Append('|').Append(item.Hash); } if (stringBuilder.Length > 24576) { throw new InvalidDataException("Clan media manifest exceeds its supported size."); } return stringBuilder.ToString(); } private static List ParseManifest(string manifest) { if (string.IsNullOrEmpty(manifest)) { return new List(); } if (manifest.Length > 24576 || manifest.IndexOf('\r') >= 0) { throw new InvalidDataException("Clan media manifest is too large or is not canonical."); } string[] array = manifest.Split(new char[1] { '\n' }); if (array.Length == 0 || !StringComparer.Ordinal.Equals(array[0], "v4")) { throw new InvalidDataException("Clan media manifest has an unsupported version."); } List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); MediaRole mediaRole = MediaRole.Emoji; string x = ""; int num = 0; int num2 = 0; int num3 = 0; for (int i = 1; i < array.Length; i++) { string obj = array[i]; if (obj.Length == 0) { throw new InvalidDataException("Clan media manifest contains an empty record."); } string[] array2 = obj.Split(new char[1] { '|' }); if (array2.Length != 5) { throw new InvalidDataException("Clan media manifest record has an invalid field count."); } string text = array2[0]; MediaRole mediaRole2; if (!(text == "e")) { if (!(text == "m")) { throw new InvalidDataException("Clan media manifest contains an unknown role."); } mediaRole2 = MediaRole.Emblem; } else { mediaRole2 = MediaRole.Emoji; } MediaRole mediaRole3 = mediaRole2; text = array2[1]; EmojiFileKind emojiFileKind; if (!(text == "p")) { if (!(text == "g")) { throw new InvalidDataException("Clan media manifest contains an unknown file type."); } emojiFileKind = EmojiFileKind.Gif; } else { emojiFileKind = EmojiFileKind.Png; } EmojiFileKind emojiFileKind2 = emojiFileKind; if (mediaRole3 == MediaRole.Emblem && emojiFileKind2 != EmojiFileKind.Png) { throw new InvalidDataException("Clan media manifest contains a non-PNG emblem."); } string text2 = array2[2]; string item = array2[0] + "|" + text2; if (!IsSafeName(text2) || !hashSet.Add(item)) { throw new InvalidDataException("Clan media manifest contains an invalid or duplicate name."); } if (list.Count > 0 && (mediaRole3 < mediaRole || (mediaRole3 == mediaRole && StringComparer.Ordinal.Compare(x, text2) >= 0))) { throw new InvalidDataException("Clan media manifest records are not strictly sorted."); } mediaRole = mediaRole3; x = text2; if (!int.TryParse(array2[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0 || result > GetMaximumSourceBytes(emojiFileKind2) || !StringComparer.Ordinal.Equals(array2[3], result.ToString(CultureInfo.InvariantCulture))) { throw new InvalidDataException("Clan media manifest contains an invalid file length."); } if (!IsSha256(array2[4])) { throw new InvalidDataException("Clan media manifest contains an invalid SHA-256 hash."); } if (mediaRole3 == MediaRole.Emblem) { num3++; } else if (emojiFileKind2 == EmojiFileKind.Png) { num++; } else { num2++; } list.Add(new ManifestRecord(mediaRole3, emojiFileKind2, text2, result, array2[4])); } if (num > 100 || num2 > 50 || num + num2 > 100 || num3 > 50) { throw new InvalidDataException("Clan media manifest contains too many files."); } if (!StringComparer.Ordinal.Equals(manifest, SerializeManifest(list))) { throw new InvalidDataException("Clan media manifest is not in canonical form."); } return list; } private static void OnSyncedManifestChanged() { ZNet instance = ZNet.instance; if (_initialized && !GUIManager.IsHeadless() && !((Object)(object)instance == (Object)null) && !instance.IsServer()) { QueueManifest(SyncedManifest.Value); } } private static void QueueManifest(string manifest) { _pendingManifest = new PendingManifestState(manifest, ZNet.instance); } private static void StartBuild(string manifest) { CancelBuild(); _waitingManifest = ""; _waitingRecords = null; CancelEmojiClientCatalog(); try { List list = ParseManifest(manifest); if (list.Count == 0) { ReplaceRuntime(null); _preparedSources.Clear(); _activeManifest = ""; _activeRecords = Array.Empty(); } else if (!StringComparer.Ordinal.Equals(_activeManifest, manifest) || _runtime == null) { _waitingManifest = manifest; _waitingRecords = list; BeginEmojiClientCatalog(manifest, list); } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Synchronized Clan media manifest was rejected: " + ex.Message)); } } private static void StartReadyEmojiBuild() { if (_waitingRecords != null && IsEmojiClientCatalogReady(_waitingManifest)) { _build = new BuildState(_waitingManifest, _waitingRecords, ZNet.instance); _waitingManifest = ""; _waitingRecords = null; } } private static void CancelBuild() { BuildState? build = _build; _build = null; build?.Cancel(); } private static void ProcessBuildStep() { BuildState build = _build; if (build == null) { return; } if (build.Session != ZNet.instance) { CancelBuild(); return; } if (build.PendingGifTask != null) { if (!build.PendingGifTask.IsCompleted) { return; } ManifestRecord pendingGifRecord = build.PendingGifRecord; Task pendingGifTask = build.PendingGifTask; build.PendingGifRecord = null; build.PendingGifTask = null; try { AddBuildSource(build, pendingGifTask.GetAwaiter().GetResult()); return; } catch (Exception error) { RecordBuildFailure(build, pendingGifRecord, error); return; } } if (build.NextRecordIndex < build.Records.Count) { ManifestRecord record = build.Records[build.NextRecordIndex++]; if (build.PreparedSources.TryGetValue(SourceCacheKey(record), out SourceEmoji value)) { AddBuildSource(build, value.WithRecord(record)); return; } if (record.Kind == EmojiFileKind.Gif) { try { byte[] data = ReadSyncedEmojiFile(record); build.PendingGifRecord = record; CancellationToken cancellationToken = build.Cancellation.Token; build.PendingGifTask = Task.Run(() => LoadGifSourceSerialized(record, data, cancellationToken), cancellationToken); build.PendingGifTask.ContinueWith((Task completed) => completed.Exception, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); return; } catch (Exception error2) { RecordBuildFailure(build, record, error2); return; } } try { byte[] data2 = ReadSyncedEmojiFile(record); SourceEmoji source = LoadPngSource(record, data2); AddBuildSource(build, source); return; } catch (Exception error3) { RecordBuildFailure(build, record, error3); return; } } if (build.Failures > 0 || build.Sources.Count + build.GifFrameBudgetSkips != build.Records.Count) { CancelBuild(); ClanPlugin.ClanLogger.LogWarning((object)($"Clan media update was not applied because {build.Failures} synchronized files failed validation. " + "The previous media set remains active.")); return; } if (build.Sources.Count == 0) { CancelBuild(); ClanPlugin.ClanLogger.LogWarning((object)"Clan media update contained no files within the client GIF frame budget. The previous media set remains active."); return; } if (build.RuntimeBuild == null) { try { build.RuntimeBuild = new RuntimeBuildState(build.Manifest, build.Sources); return; } catch (Exception ex) { CancelBuild(); ClanPlugin.ClanLogger.LogWarning((object)("Clan media runtime could not be prepared; the previous set remains active: " + ex.Message)); return; } } try { RuntimeLibrary runtimeLibrary = ProcessRuntimeBuildStep(build.RuntimeBuild); if (runtimeLibrary == null) { return; } Dictionary preparedSources = build.Sources.GroupBy((SourceEmoji sourceEmoji) => SourceCacheKey(sourceEmoji.Record), StringComparer.Ordinal).ToDictionary, string, SourceEmoji>((IGrouping group) => group.Key, (IGrouping group) => group.First(), StringComparer.Ordinal); build.RuntimeBuild.ReleaseOwnership(); try { ReplaceRuntime(runtimeLibrary); } catch { if (_runtime != runtimeLibrary) { DestroyRuntime(runtimeLibrary); } throw; } _build = null; _activeManifest = build.Manifest; _activeRecords = build.Records; _preparedSources = preparedSources; PruneEmojiCache(build.Records); ClanPlugin.ClanLogger.LogInfo((object)($"Applied {build.Sources.Count} synchronized Clan media files" + ((build.GifFrameBudgetSkips == 0) ? "." : ($"; skipped {build.GifFrameBudgetSkips} GIF file(s) outside the " + $"{3000}-frame client budget.")))); } catch (Exception ex2) { CancelBuild(); ClanPlugin.ClanLogger.LogWarning((object)("Clan media runtime could not be built; the previous set remains active: " + ex2.Message)); } } private static void AddBuildSource(BuildState build, SourceEmoji source) { if (source.Record.Kind == EmojiFileKind.Gif) { int count = source.Frames.Count; if (count > 3000 - build.TotalGifFrames) { build.GifFrameBudgetSkips++; ClanPlugin.ClanLogger.LogWarning((object)("Clan emoji '" + source.Record.Name + source.Record.Extension + "' was skipped on this client: " + $"the synchronized catalog would exceed its {3000} total GIF frame budget.")); return; } build.TotalGifFrames += count; } build.Sources.Add(source); build.PreparedSources[SourceCacheKey(source.Record)] = source; } private static SourceEmoji LoadGifSourceSerialized(ManifestRecord record, byte[] data, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); GifDecodeGate.Wait(cancellationToken); try { cancellationToken.ThrowIfCancellationRequested(); SourceEmoji result = LoadGifSource(record, data); cancellationToken.ThrowIfCancellationRequested(); return result; } finally { GifDecodeGate.Release(); } } private static void RecordBuildFailure(BuildState build, ManifestRecord record, Exception error) { build.Failures++; ClanPlugin.ClanLogger.LogWarning((object)("Clan " + ((record.Role == MediaRole.Emoji) ? "emoji" : "emblem") + " '" + record.Name + "' is unavailable on this client: " + error.Message)); } private static string SourceCacheKey(ManifestRecord record) { return record.Kind.ToString() + ":" + record.Hash; } private static SourceEmoji LoadPngSource(ManifestRecord record, byte[] data) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0049: Expected O, but got Unknown ValidatePngHeader(data, out var width, out var height); Texture2D val = null; try { val = new Texture2D(2, 2, (TextureFormat)4, false) { name = "Clan Emoji Source " + record.Name, hideFlags = (HideFlags)61, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)1, anisoLevel = 0 }; if (!AssetUtils.LoadImage(val, data)) { throw new InvalidDataException("Unity could not decode the PNG."); } if (((Texture)val).width != width || ((Texture)val).height != height) { throw new InvalidDataException("Decoded PNG dimensions do not match its header."); } ResizeToRenderedBounds(GetTopDownRgba(val), ((Texture)val).width, ((Texture)val).height, out byte[] rendered, out int renderedWidth, out int renderedHeight); return new SourceEmoji(record, renderedWidth, renderedHeight, new List { rendered }, 0); } finally { SafeDestroy((Object?)(object)val); } } private static SourceEmoji LoadGifSource(ManifestRecord record, byte[] data) { ClanGifDecoder.Animation animation = ClanGifDecoder.Decode(data, 2097152, 512, 180, 8388608L); long num = ValidateGifAnimation(animation); int count = animation.Frames.Count; int framesPerSecond = ((count != 1) ? Clamp((int)Math.Round((double)count * 1000.0 / (double)num, MidpointRounding.AwayFromZero), 1, 50) : 0); List list = new List(count); int renderedWidth = 0; int renderedHeight = 0; foreach (ClanGifDecoder.Frame frame in animation.Frames) { ResizeToRenderedBounds(frame.Rgba32, animation.Width, animation.Height, out byte[] rendered, out renderedWidth, out renderedHeight); list.Add(rendered); } return new SourceEmoji(record, renderedWidth, renderedHeight, list, framesPerSecond); } private static long ValidateGifAnimation(ClanGifDecoder.Animation animation) { if (animation.Frames.Count == 0) { throw new InvalidDataException("GIF does not contain an image frame."); } if (animation.Frames.Any((ClanGifDecoder.Frame frame) => frame.RequiresUserInput)) { throw new InvalidDataException("GIF frames requiring user input are not supported."); } if (animation.Frames.Count > 1 && !animation.LoopsForever) { throw new InvalidDataException("Animated GIF must declare infinite looping."); } long num = animation.Frames.Aggregate(0L, (long current, ClanGifDecoder.Frame frame) => current + Math.Max(20, frame.DelayMilliseconds)); if (num <= 0 || num > 10000) { throw new InvalidDataException($"GIF duration must be between 1 and {10000L} milliseconds."); } return num; } private static void ResizeToRenderedBounds(byte[] source, int sourceWidth, int sourceHeight, out byte[] rendered, out int renderedWidth, out int renderedHeight) { float num = 96f / (float)Math.Max(sourceWidth, sourceHeight); renderedWidth = Math.Max(1, RoundPositive((float)sourceWidth * num)); renderedHeight = Math.Max(1, RoundPositive((float)sourceHeight * num)); rendered = ((renderedWidth == sourceWidth && renderedHeight == sourceHeight) ? source : ResizeRgbaBilinear(source, sourceWidth, sourceHeight, renderedWidth, renderedHeight)); } private static byte[] GetTopDownRgba(Texture2D texture) { //IL_003c: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) Color32[] pixels = texture.GetPixels32(); byte[] array = new byte[checked(((Texture)texture).width * ((Texture)texture).height * 4)]; for (int i = 0; i < ((Texture)texture).height; i++) { int num = ((Texture)texture).height - 1 - i; for (int j = 0; j < ((Texture)texture).width; j++) { Color32 val = pixels[num * ((Texture)texture).width + j]; int num2 = (i * ((Texture)texture).width + j) * 4; array[num2] = val.r; array[num2 + 1] = val.g; array[num2 + 2] = val.b; array[num2 + 3] = val.a; } } return array; } private static byte[] ResizeRgbaBilinear(byte[] source, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight) { byte[] array = new byte[checked(targetWidth * targetHeight * 4)]; float num = (float)sourceWidth / (float)targetWidth; float num2 = (float)sourceHeight / (float)targetHeight; for (int i = 0; i < targetHeight; i++) { float num3 = ((float)i + 0.5f) * num2 - 0.5f; int num4 = Clamp((int)Math.Floor(num3), 0, sourceHeight - 1); int num5 = Math.Min(num4 + 1, sourceHeight - 1); float num6 = Clamp01(num3 - (float)num4); for (int j = 0; j < targetWidth; j++) { float num7 = ((float)j + 0.5f) * num - 0.5f; int num8 = Clamp((int)Math.Floor(num7), 0, sourceWidth - 1); int num9 = Math.Min(num8 + 1, sourceWidth - 1); float num10 = Clamp01(num7 - (float)num8); int num11 = (num4 * sourceWidth + num8) * 4; int num12 = (num4 * sourceWidth + num9) * 4; int num13 = (num5 * sourceWidth + num8) * 4; int num14 = (num5 * sourceWidth + num9) * 4; int num15 = (i * targetWidth + j) * 4; float num16 = (1f - num10) * (1f - num6); float num17 = num10 * (1f - num6); float num18 = (1f - num10) * num6; float num19 = num10 * num6; float num20 = (float)(int)source[num11 + 3] * num16 + (float)(int)source[num12 + 3] * num17 + (float)(int)source[num13 + 3] * num18 + (float)(int)source[num14 + 3] * num19; array[num15 + 3] = (byte)Clamp(RoundPositive(num20), 0, 255); if (num20 <= 0.001f) { array[num15] = 0; array[num15 + 1] = 0; array[num15 + 2] = 0; continue; } for (int k = 0; k < 3; k++) { float num21 = (float)(source[num11 + k] * source[num11 + 3]) * num16 + (float)(source[num12 + k] * source[num12 + 3]) * num17 + (float)(source[num13 + k] * source[num13 + 3]) * num18 + (float)(source[num14 + k] * source[num14 + 3]) * num19; array[num15 + k] = (byte)Clamp(RoundPositive(num21 / num20), 0, 255); } } } return array; } private static int RoundPositive(float value) { return (int)Math.Floor(value + 0.5f); } private static int Clamp(int value, int minimum, int maximum) { if (value >= minimum) { if (value <= maximum) { return value; } return maximum; } return minimum; } private static float Clamp01(float value) { if (!(value < 0f)) { if (!(value > 1f)) { return value; } return 1f; } return 0f; } private static RuntimeLibrary? ProcessRuntimeBuildStep(RuntimeBuildState build) { switch (build.Stage) { case RuntimeBuildStage.StaticEmoji: BuildStaticEmojiSheet(build); build.Stage = RuntimeBuildStage.AnimatedEmoji; return null; case RuntimeBuildStage.AnimatedEmoji: if (build.NextAnimatedSourceIndex < build.AnimatedSources.Count) { BuildAnimatedEmojiSheet(build, build.AnimatedSources[build.NextAnimatedSourceIndex++]); } if (build.NextAnimatedSourceIndex >= build.AnimatedSources.Count) { build.Stage = RuntimeBuildStage.Emblems; } return null; case RuntimeBuildStage.Emblems: BuildEmblemSheet(build); build.Stage = RuntimeBuildStage.Finalize; return null; case RuntimeBuildStage.Finalize: { RuntimeLibrary result = FinalizeRuntimeLibrary(build); build.Stage = RuntimeBuildStage.Completed; return result; } default: throw new InvalidOperationException("Clan media runtime build is already complete."); } } private static void BuildStaticEmojiSheet(RuntimeBuildState build) { if (build.StillSources.Count != 0) { List frames = build.StillSources.Select((SourceEmoji source) => new FrameSpec(source, 0, SpriteName(source.Record, 0, animated: false))).ToList(); RuntimeSheet sheet = BuildRuntimeSheet("ClanEmoji_Still_" + build.ManifestHash, frames); TrackRuntimeSheet(build, sheet, isEmoji: true); for (int num = 0; num < build.StillSources.Count; num++) { build.References[build.StillSources[num]] = new SourceRuntimeReference(sheet, num); } } } private static void BuildAnimatedEmojiSheet(RuntimeBuildState build, SourceEmoji source) { List frames = (from index in Enumerable.Range(0, source.Frames.Count) select new FrameSpec(source, index, SpriteName(source.Record, index, animated: true))).ToList(); RuntimeSheet sheet = BuildRuntimeSheet("ClanEmoji_" + source.Record.Name + "_" + source.Record.Hash.Substring(0, 12), frames); TrackRuntimeSheet(build, sheet, isEmoji: true); build.References[source] = new SourceRuntimeReference(sheet, 0); } private static void BuildEmblemSheet(RuntimeBuildState build) { if (build.EmblemSources.Count != 0) { List frames = build.EmblemSources.Select((SourceEmoji source) => new FrameSpec(source, 0, SpriteName(source.Record, 0, animated: false))).ToList(); RuntimeSheet sheet = BuildRuntimeSheet("ClanEmblem_Still_" + build.ManifestHash, frames); TrackRuntimeSheet(build, sheet, isEmoji: false); for (int num = 0; num < build.EmblemSources.Count; num++) { build.References[build.EmblemSources[num]] = new SourceRuntimeReference(sheet, num); } } } private static void TrackRuntimeSheet(RuntimeBuildState build, RuntimeSheet sheet, bool isEmoji) { try { build.Sheets.Add(sheet); if (isEmoji) { build.EmojiSheets.Add(sheet); } } catch { build.EmojiSheets.Remove(sheet); build.Sheets.Remove(sheet); DestroyRuntimeSheet(sheet); throw; } } private static RuntimeLibrary FinalizeRuntimeLibrary(RuntimeBuildState build) { List sheets = build.Sheets; List emojiSheets = build.EmojiSheets; Dictionary references = build.References; IReadOnlyList emojiSources = build.EmojiSources; IReadOnlyList emblemSources = build.EmblemSources; if (sheets.Count == 0) { throw new InvalidOperationException("No Clan media sprite sheets were produced."); } TMP_SpriteAsset val = ((emojiSheets.Count == 0) ? null : emojiSheets[0].SpriteAsset); if ((Object)(object)val != (Object)null) { TMP_SpriteAsset val2 = val; if (val2.fallbackSpriteAssets == null) { val2.fallbackSpriteAssets = new List(); } for (int i = 1; i < emojiSheets.Count; i++) { val.fallbackSpriteAssets.Add(emojiSheets[i].SpriteAsset); } } List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (SourceEmoji item in emojiSources) { SourceRuntimeReference sourceRuntimeReference = references[item]; Sprite val3 = sourceRuntimeReference.Sheet.Sprites[sourceRuntimeReference.FirstFrameIndex]; string name = ((Object)val3).name; string text = ""; string animatedTag = ((item.Frames.Count == 1) ? text : $""); string text2 = TokenForName(item.Record.Name); list.Add(new RuntimeEmoji(text2, val3, item.Record.Kind == EmojiFileKind.Gif)); RuntimeRenderTag value = (dictionary[text2] = new RuntimeRenderTag(animatedTag, text, item.Frames.Count > 1)); dictionary2[name] = value; } Regex tokenRegex = ((list.Count == 0) ? new Regex("(?!)", RegexOptions.Compiled | RegexOptions.CultureInvariant) : new Regex(string.Join("|", (from entry in list select entry.Token into token orderby token.Length descending select token).Select(Regex.Escape)), RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant)); Regex spriteTagRegex = ((dictionary2.Count == 0) ? new Regex("(?!)", RegexOptions.Compiled | RegexOptions.CultureInvariant) : new Regex("" + string.Join("|", dictionary2.Keys.OrderByDescending((string text3) => text3.Length).Select(Regex.Escape)) + ")\"(?:\\s+anim=\"[^\"]*\")?\\s*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant)); List list2 = emblemSources.Select(delegate(SourceEmoji source) { SourceRuntimeReference sourceRuntimeReference2 = references[source]; return new ClanEmblemPickerItem(source.Record.Name, sourceRuntimeReference2.Sheet.Sprites[sourceRuntimeReference2.FirstFrameIndex]); }).ToList(); Dictionary emblemsByName = list2.ToDictionary((ClanEmblemPickerItem entry) => entry.Name, StringComparer.Ordinal); return new RuntimeLibrary(val, sheets.ToArray(), list, list2.AsReadOnly(), emblemsByName, dictionary, tokenRegex, dictionary2, spriteTagRegex); } private static RuntimeSheet BuildRuntimeSheet(string assetName, IReadOnlyList frames) { //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Expected O, but got Unknown //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Expected O, but got Unknown //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_0440: Unknown result type (might be due to invalid IL or missing references) //IL_0442: Unknown result type (might be due to invalid IL or missing references) //IL_044c: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Expected O, but got Unknown //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_046a: Unknown result type (might be due to invalid IL or missing references) //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_0484: Expected O, but got Unknown if (frames.Count == 0) { throw new ArgumentException("A sprite sheet requires at least one frame.", "frames"); } if (SpriteAssetVersionField == null) { throw new MissingFieldException(typeof(TMP_Asset).FullName, "m_Version"); } int num = frames.Max((FrameSpec frame) => frame.Source.Width); int num2 = frames.Max((FrameSpec frame) => frame.Source.Height); int num3 = Math.Min(5, Math.Max(1, (int)Math.Ceiling(Math.Sqrt(frames.Count)))); int num4 = (frames.Count + num3 - 1) / num3; int num5 = num + 4; int num6 = num2 + 4; int num7; int num8; byte[] array; GlyphRect[] array2; checked { num7 = num3 * num5; num8 = num4 * num6; array = new byte[num7 * num8 * 4]; array2 = (GlyphRect[])(object)new GlyphRect[frames.Count]; } for (int num9 = 0; num9 < frames.Count; num9++) { FrameSpec frameSpec = frames[num9]; int num10 = num9 % num3; int num11 = num9 / num3; int num12 = num10 * num5 + 2 + (num - frameSpec.Source.Width) / 2; int num13 = num8 - (num11 + 1) * num6 + 2 + (num2 - frameSpec.Source.Height) / 2; BlitWithGutter(frameSpec.Pixels, frameSpec.Source.Width, frameSpec.Source.Height, array, num7, num8, num12, num13, 2); array2[num9] = new GlyphRect(num12, num13, frameSpec.Source.Width, frameSpec.Source.Height); } Texture2D val = null; TMP_SpriteAsset val2 = null; Material val3 = null; Sprite[] array3 = Array.Empty(); try { val = new Texture2D(num7, num8, (TextureFormat)4, false) { name = assetName + " Texture", hideFlags = (HideFlags)61, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)1, anisoLevel = 0 }; val.LoadRawTextureData(array); val.Apply(false, true); val2 = ScriptableObject.CreateInstance(); ((Object)val2).name = assetName; ((Object)val2).hideFlags = (HideFlags)61; SpriteAssetVersionField.SetValue(val2, "1.1.0"); val2.spriteSheet = (Texture)(object)val; val2.spriteInfoList = new List(); val2.fallbackSpriteAssets = new List(); ((TMP_Asset)val2).hashCode = TMP_TextUtilities.GetSimpleHashCode(assetName); TMP_SpriteAsset obj = val2; FaceInfo faceInfo = default(FaceInfo); ((FaceInfo)(ref faceInfo)).familyName = assetName; ((FaceInfo)(ref faceInfo)).styleName = "Regular"; ((FaceInfo)(ref faceInfo)).pointSize = 96f; ((FaceInfo)(ref faceInfo)).scale = 1f; ((FaceInfo)(ref faceInfo)).lineHeight = 96f; ((FaceInfo)(ref faceInfo)).ascentLine = 96f; ((FaceInfo)(ref faceInfo)).capLine = 96f; ((FaceInfo)(ref faceInfo)).meanLine = 48f; ((FaceInfo)(ref faceInfo)).baseline = 0f; ((FaceInfo)(ref faceInfo)).descentLine = 0f; ((TMP_Asset)obj).faceInfo = faceInfo; ShaderUtilities.GetShaderPropertyIDs(); Shader obj2 = Shader.Find("TextMeshPro/Sprite"); if ((Object)(object)obj2 == (Object)null) { throw new InvalidOperationException("TextMeshPro/Sprite shader was not found."); } val3 = new Material(obj2) { name = assetName + " Material", hideFlags = (HideFlags)61 }; val3.SetTexture(ShaderUtilities.ID_MainTex, (Texture)(object)val); ((TMP_Asset)val2).material = val3; ((TMP_Asset)val2).materialHashCode = TMP_TextUtilities.GetSimpleHashCode(((Object)val3).name); List spriteGlyphTable = val2.spriteGlyphTable; List spriteCharacterTable = val2.spriteCharacterTable; spriteGlyphTable.Clear(); spriteCharacterTable.Clear(); array3 = (Sprite[])(object)new Sprite[frames.Count]; Rect val5 = default(Rect); GlyphMetrics val7 = default(GlyphMetrics); for (int num14 = 0; num14 < frames.Count; num14++) { FrameSpec frameSpec2 = frames[num14]; GlyphRect val4 = array2[num14]; ((Rect)(ref val5))..ctor((float)((GlyphRect)(ref val4)).x, (float)((GlyphRect)(ref val4)).y, (float)((GlyphRect)(ref val4)).width, (float)((GlyphRect)(ref val4)).height); Sprite val6 = Sprite.Create(val, val5, new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, Vector4.zero, false); ((Object)val6).name = frameSpec2.SpriteName; ((Object)val6).hideFlags = (HideFlags)61; array3[num14] = val6; ((GlyphMetrics)(ref val7))..ctor((float)frameSpec2.Source.Width, (float)frameSpec2.Source.Height, 0f, (float)frameSpec2.Source.Height, (float)frameSpec2.Source.Width); TMP_SpriteGlyph val8 = new TMP_SpriteGlyph((uint)num14, val7, val4, 1f, 0, val6); spriteGlyphTable.Add(val8); TMP_SpriteCharacter item = new TMP_SpriteCharacter(65534u, val2, val8) { name = frameSpec2.SpriteName, scale = 1f }; spriteCharacterTable.Add(item); } val2.UpdateLookupTables(); return new RuntimeSheet(val, val2, val3, array3); } catch { Sprite[] array4 = array3; for (int num15 = 0; num15 < array4.Length; num15++) { SafeDestroy((Object?)(object)array4[num15]); } SafeDestroy((Object?)(object)val3); SafeDestroy((Object?)(object)val2); SafeDestroy((Object?)(object)val); throw; } } private static void BlitWithGutter(byte[] source, int sourceWidth, int sourceHeight, byte[] atlas, int atlasWidth, int atlasHeight, int contentX, int contentY, int gutter) { for (int i = -gutter; i < sourceHeight + gutter; i++) { int num = Mathf.Clamp(i, 0, sourceHeight - 1); int num2 = contentY + sourceHeight - 1 - i; if (num2 < 0 || num2 >= atlasHeight) { continue; } for (int j = -gutter; j < sourceWidth + gutter; j++) { int num3 = Mathf.Clamp(j, 0, sourceWidth - 1); int num4 = contentX + j; if (num4 >= 0 && num4 < atlasWidth) { int num5 = (num * sourceWidth + num3) * 4; int num6 = (num2 * atlasWidth + num4) * 4; atlas[num6] = source[num5]; atlas[num6 + 1] = source[num5 + 1]; atlas[num6 + 2] = source[num5 + 2]; atlas[num6 + 3] = source[num5 + 3]; } } } } private static void ReplaceRuntime(RuntimeLibrary? next) { if (_runtime == next) { return; } RuntimeLibrary runtime = _runtime; DetachOutput(); _runtime = next; try { try { EnsureOutputAttachment(); } catch (Exception ex) { try { ClanPlugin.ClanLogger.LogWarning((object)("Clan media UI attachment will be retried: " + ex.Message)); } catch { } } NotifyChanged(); } finally { DestroyRuntime(runtime); } } private static void EnsureOutputAttachment() { if (!ClanVanillaChatDock.SupportsCurrentChatUi) { DetachOutput(); return; } TMP_Text val = (TMP_Text)(object)(((Object)(object)Chat.instance == (Object)null) ? null : ((Terminal)Chat.instance).m_output); TMP_SpriteAsset val2 = _runtime?.RootSpriteAsset; if ((Object)(object)val2 == (Object)null || (Object)(object)val == (Object)null) { DetachOutput(); } else { if ((Object)(object)_attachedOutput == (Object)(object)val && IsRuntimeAttached(val, val2)) { return; } DetachOutput(); _attachedOutput = val; if ((Object)(object)val.spriteAsset == (Object)null) { val.spriteAsset = val2; _installedAsPrimary = true; } else if ((Object)(object)val.spriteAsset != (Object)(object)val2) { TMP_SpriteAsset spriteAsset = val.spriteAsset; TMP_SpriteAsset val3 = spriteAsset; if (val3.fallbackSpriteAssets == null) { val3.fallbackSpriteAssets = new List(); } if (!spriteAsset.fallbackSpriteAssets.Contains(val2)) { spriteAsset.fallbackSpriteAssets.Add(val2); _addedFallback = true; } _fallbackOwner = spriteAsset; } RefreshOutput(val); } } private static bool IsRuntimeAttached(TMP_Text output, TMP_SpriteAsset root) { if ((Object)(object)output.spriteAsset == (Object)(object)root) { return true; } if ((Object)(object)output.spriteAsset != (Object)null && output.spriteAsset.fallbackSpriteAssets != null) { return output.spriteAsset.fallbackSpriteAssets.Contains(root); } return false; } private static void DetachOutput() { TMP_Text attachedOutput = _attachedOutput; TMP_SpriteAsset val = _runtime?.RootSpriteAsset; if ((Object)(object)attachedOutput != (Object)null) { TMP_SpriteAnimator component = ((Component)attachedOutput).GetComponent(); if (component != null) { component.StopAllAnimations(); } } if (_addedFallback && (Object)(object)_fallbackOwner != (Object)null && (Object)(object)val != (Object)null && _fallbackOwner.fallbackSpriteAssets != null) { _fallbackOwner.fallbackSpriteAssets.Remove(val); } if (_installedAsPrimary && (Object)(object)attachedOutput != (Object)null && (Object)(object)val != (Object)null && (Object)(object)attachedOutput.spriteAsset == (Object)(object)val) { attachedOutput.spriteAsset = null; } if ((Object)(object)attachedOutput != (Object)null) { RefreshOutput(attachedOutput); TMP_SpriteAnimator component2 = ((Component)attachedOutput).GetComponent(); if (component2 != null) { component2.StopAllAnimations(); } } _fallbackOwner = null; _attachedOutput = null; _installedAsPrimary = false; _addedFallback = false; } private static void RefreshOutput(TMP_Text output) { output.havePropertiesChanged = true; ((Graphic)output).SetVerticesDirty(); try { output.ForceMeshUpdate(true, true); } catch (Exception ex) { ClanPlugin.ClanLogger.LogDebug((object)("Clan emoji output refresh was deferred: " + ex.Message)); } } private static bool ContainsEmojiMarker(string? text) { if (string.IsNullOrEmpty(text)) { return false; } if (text.IndexOf(":clan_", StringComparison.OrdinalIgnoreCase) < 0) { return text.IndexOf("clan_emoji_", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static void StopChatOutputAnimationsBeforeRewrite(Terminal terminal) { TMP_Text output = (TMP_Text)(object)terminal.m_output; if ((Object)(object)output != (Object)null && (Object)(object)output == (Object)(object)_attachedOutput) { TMP_SpriteAnimator component = ((Component)output).GetComponent(); if (component != null) { component.StopAllAnimations(); } } } private static void NotifyChanged() { PublishMediaChanged(ClanEmoji.EmojiChanged, "emoji"); PublishMediaChanged(ClanEmoji.EmblemsChanged, "emblem"); } private static void PublishMediaChanged(Action? handlers, string mediaKind) { if (handlers == null) { return; } Delegate[] invocationList = handlers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Clan " + mediaKind + " UI refresh failed: " + ex.Message)); } } } private static void DestroyRuntime(RuntimeLibrary? runtime) { if (runtime == null) { return; } runtime.RootSpriteAsset?.fallbackSpriteAssets?.Clear(); foreach (RuntimeSheet sheet in runtime.Sheets) { DestroyRuntimeSheet(sheet); } } private static void DestroyRuntimeSheet(RuntimeSheet sheet) { Sprite[] sprites = sheet.Sprites; for (int i = 0; i < sprites.Length; i++) { SafeDestroy((Object?)(object)sprites[i]); } SafeDestroy((Object?)(object)sheet.Material); SafeDestroy((Object?)(object)sheet.SpriteAsset); SafeDestroy((Object?)(object)sheet.Texture); } private static void SafeDestroy(Object? value) { if (value != (Object)null) { Object.Destroy(value); } } private static void ValidatePngHeader(byte[] data, out int width, out int height) { if (data.Length < 33 || data.Length > 524288) { throw new InvalidDataException("PNG file has an invalid size."); } for (int i = 0; i < PngSignature.Length; i++) { if (data[i] != PngSignature[i]) { throw new InvalidDataException("File does not have a valid PNG signature."); } } if (ReadUInt32BigEndian(data, 8) != 13 || data[12] != 73 || data[13] != 72 || data[14] != 68 || data[15] != 82) { throw new InvalidDataException("PNG IHDR must be the first chunk."); } uint num = ReadUInt32BigEndian(data, 16); uint num2 = ReadUInt32BigEndian(data, 20); if (num == 0 || num2 == 0 || num > 512 || num2 > 512) { throw new InvalidDataException("PNG dimensions are outside the supported range."); } byte num3 = data[24]; int num4 = data[25]; if (num3 != 8 || (num4 != 0 && num4 != 2 && num4 != 3 && num4 != 4 && num4 != 6) || data[26] != 0 || data[27] != 0 || data[28] != 0) { throw new InvalidDataException("PNG must use 8-bit color, standard compression/filtering, and no interlace."); } width = (int)num; height = (int)num2; } private static uint ReadUInt32BigEndian(byte[] data, int offset) { return (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } private static string ComputeSha256(byte[] data) { using SHA256 sHA = SHA256.Create(); return string.Concat(from value in sHA.ComputeHash(data) select value.ToString("x2", CultureInfo.InvariantCulture)); } private static bool IsSha256(string value) { if (value.Length != 64) { return false; } foreach (char c in value) { bool num = c >= '0' && c <= '9'; bool flag = c >= 'a' && c <= 'f'; if (!num && !flag) { return false; } } return true; } private static bool IsSafeName(string value) { if (SafeNameRegex.IsMatch(value)) { return !ReservedWindowsNames.Contains(value); } return false; } private static HashSet BuildReservedWindowsNames() { HashSet hashSet = new HashSet(StringComparer.Ordinal) { "con", "prn", "aux", "nul" }; for (int i = 1; i <= 9; i++) { hashSet.Add("com" + i.ToString(CultureInfo.InvariantCulture)); hashSet.Add("lpt" + i.ToString(CultureInfo.InvariantCulture)); } return hashSet; } private static string TokenForName(string name) { return ":clan_" + name + ":"; } private static string SpriteName(ManifestRecord record, int frameIndex, bool animated) { string text = ((record.Role == MediaRole.Emblem) ? "clan_emblem_" : ((record.Kind != EmojiFileKind.Gif) ? "clan_emoji_png_" : "clan_emoji_gif_")); string text2 = text; if (!animated) { return text2 + record.Name; } return text2 + record.Name + "_f" + frameIndex.ToString("000", CultureInfo.InvariantCulture); } private static bool IsCurrentEmojiServerManifest(string manifest) { if (_serverEmojiCatalog != null) { return StringComparer.Ordinal.Equals(_serverEmojiCatalog.Manifest, manifest); } return false; } private static void InitializeEmojiCache() { Directory.CreateDirectory(EmojiCacheDirectory); DeletePartialEmojiCacheFiles(); } private static void ResetEmojiSyncSession(ZNet? session) { _clientEmojiCatalog = null; _serverEmojiCatalog = null; EmojiPeerBudgets.Clear(); EmojiGlobalTransferBudget.Reset(); Interlocked.Exchange(ref _emojiReloadRequestedAtTicks, 0L); Interlocked.Exchange(ref _emojiReloadFailures, 0); Interlocked.Exchange(ref _emojiWatcherNeedsRestart, 0); Interlocked.Exchange(ref _emojiWatcherRestartFailures, 0); _serverManifestPublishRetryPending = false; DisposeEmojiFileWatcher(); if (session != null && session.IsServer()) { CreateEmojiFileWatcher(); } } internal static void RegisterEmojiFileRpc(ZNet znet, ZNetPeer peer) { if (znet.IsServer()) { peer.m_rpc.Register(EmojiFileRequestRpc, (Action)delegate(ZRpc rpc, ZPackage package) { HandleEmojiFileRequest(peer, rpc, package); }); } else { peer.m_rpc.Register(EmojiFileResponseRpc, (Action)HandleEmojiFileResponse); } } private static ServerEmojiCatalog PrepareEmojiServerCatalog(string manifest, IReadOnlyList records, IReadOnlyCollection blobs) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); int num = 0; foreach (ServerEmojiBlob blob in blobs) { if (blob.Data.Length != blob.Record.Length) { throw new InvalidDataException("Media source '" + blob.Record.Name + "' changed while its catalog was being published."); } if ((blob.Record.Kind == EmojiFileKind.Gif && (blob.GifFrameCount <= 0 || blob.GifFrameCount > 180)) || (blob.Record.Kind == EmojiFileKind.Png && blob.GifFrameCount != 0)) { throw new InvalidDataException("Media source '" + blob.Record.Name + "' has invalid frame metadata."); } if (blob.Record.Kind == EmojiFileKind.Gif) { if (blob.GifFrameCount > 3000 - num) { throw new InvalidDataException($"Clan media catalog exceeds its {3000} total GIF frame budget."); } num += blob.GifFrameCount; } if (dictionary.TryGetValue(blob.Record.Hash, out var value)) { if (value.Record.Length != blob.Record.Length || !StringComparer.Ordinal.Equals(value.Record.Extension, blob.Record.Extension) || value.GifFrameCount != blob.GifFrameCount) { throw new InvalidDataException("A media content hash maps to conflicting files."); } } else { dictionary.Add(blob.Record.Hash, blob); } } return new ServerEmojiCatalog(manifest, ComputeSha256(Encoding.UTF8.GetBytes(manifest)), records.ToArray(), dictionary); } private static void CommitEmojiServerCatalog(ServerEmojiCatalog catalog) { EmojiPeerBudgets.Clear(); EmojiGlobalTransferBudget.Reset(); _serverEmojiCatalog = catalog; } private static void ClearEmojiServerCatalog() { _serverEmojiCatalog = null; EmojiPeerBudgets.Clear(); EmojiGlobalTransferBudget.Reset(); } private static void BeginEmojiClientCatalog(string manifest, IReadOnlyList records) { _clientEmojiCatalog = new ClientEmojiCatalog(ZNet.instance, manifest, ComputeSha256(Encoding.UTF8.GetBytes(manifest)), records); } private static void CancelEmojiClientCatalog() { _clientEmojiCatalog = null; } private static bool IsEmojiClientCatalogReady(string manifest) { ClientEmojiCatalog clientEmojiCatalog = _clientEmojiCatalog; if (clientEmojiCatalog != null && clientEmojiCatalog.Ready && clientEmojiCatalog.Session == ZNet.instance) { return StringComparer.Ordinal.Equals(clientEmojiCatalog.Manifest, manifest); } return false; } private static void TickEmojiSync() { ClientEmojiCatalog clientEmojiCatalog = _clientEmojiCatalog; if (clientEmojiCatalog == null || clientEmojiCatalog.Ready) { return; } if (clientEmojiCatalog.Session != ZNet.instance) { _clientEmojiCatalog = null; return; } if (clientEmojiCatalog.Failed) { if (clientEmojiCatalog.CatalogRetryCount == 0 && Time.realtimeSinceStartup >= clientEmojiCatalog.CatalogRetryAt) { clientEmojiCatalog.CatalogRetryCount++; clientEmojiCatalog.Failed = false; clientEmojiCatalog.Download = null; clientEmojiCatalog.NextRecordIndex = 0; clientEmojiCatalog.ServerHits = 0; clientEmojiCatalog.CacheHits = 0; clientEmojiCatalog.ConfigHits = 0; clientEmojiCatalog.Downloads = 0; ClanPlugin.ClanLogger.LogInfo((object)"Retrying the synchronized Clan media catalog after a transient failure."); } return; } ClientEmojiDownload download = clientEmojiCatalog.Download; if (download != null) { TickEmojiDownload(clientEmojiCatalog, download); return; } if (clientEmojiCatalog.NextRecordIndex >= clientEmojiCatalog.Records.Count) { clientEmojiCatalog.Ready = true; ClanPlugin.ClanLogger.LogInfo((object)($"Clan media catalog is ready: {clientEmojiCatalog.ServerHits} server hits, " + $"{clientEmojiCatalog.CacheHits} cache hits, {clientEmojiCatalog.ConfigHits} config hits, " + $"{clientEmojiCatalog.Downloads} downloaded files.")); return; } ManifestRecord manifestRecord = clientEmojiCatalog.Records[clientEmojiCatalog.NextRecordIndex]; if (TryReadSyncedEmojiFile(manifestRecord, out byte[] _, out SyncedMediaFileSource source)) { switch (source) { case SyncedMediaFileSource.Server: clientEmojiCatalog.ServerHits++; break; case SyncedMediaFileSource.Cache: clientEmojiCatalog.CacheHits++; break; case SyncedMediaFileSource.Config: clientEmojiCatalog.ConfigHits++; break; default: throw new InvalidOperationException("A synchronized Clan media file was resolved without a source."); } clientEmojiCatalog.NextRecordIndex++; } else { ZNet instance = ZNet.instance; if (instance != null && instance.IsServer()) { FailEmojiClientCatalog(clientEmojiCatalog, "The listen server catalog does not contain '" + manifestRecord.Name + "'."); } else { clientEmojiCatalog.Download = new ClientEmojiDownload(manifestRecord); } } } private static void TickEmojiDownload(ClientEmojiCatalog catalog, ClientEmojiDownload download) { //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown float realtimeSinceStartup = Time.realtimeSinceStartup; if (download.Completed) { try { string x = ComputeSha256(download.Buffer); if (!StringComparer.Ordinal.Equals(x, download.Record.Hash)) { throw new InvalidDataException("Downloaded SHA-256 does not match the manifest."); } WriteEmojiCacheAtomically(download.Record, download.Buffer); catalog.Download = null; catalog.NextRecordIndex++; catalog.Downloads++; PruneEmojiCache(catalog.Records); return; } catch (Exception ex) { FailEmojiClientCatalog(catalog, "Downloaded media file '" + download.Record.Name + "' could not be cached: " + ex.Message); return; } } for (int i = 0; i < download.ChunkCount; i++) { if (download.IsResponseTimedOut(i, realtimeSinceStartup)) { RetryEmojiChunk(catalog, download, i, "response timed out", 0.25f); if (catalog.Failed) { return; } } } ZNet instance = ZNet.instance; ZRpc val = ((instance != null) ? instance.GetServerRPC() : null); if (val == null) { return; } ISocket socket = val.GetSocket(); if (socket == null || !socket.IsConnected()) { return; } int chunkIndex; while (download.InFlightCount < 4 && download.TryGetNextRequestChunk(realtimeSinceStartup, out chunkIndex)) { int num = chunkIndex * 65536; int num2 = download.MarkRequested(chunkIndex, realtimeSinceStartup + 5f); ZPackage val2 = new ZPackage(); val2.Write(3); val2.Write(catalog.CatalogId); val2.Write(download.Record.Hash); val2.Write(num); val2.Write(num2); try { val.Invoke(EmojiFileRequestRpc, new object[1] { val2 }); } catch (Exception ex2) { RetryEmojiChunk(catalog, download, chunkIndex, ex2.Message, 0.25f); if (catalog.Failed) { break; } } } } private static void HandleEmojiFileRequest(ZNetPeer peer, ZRpc rpc, ZPackage package) { try { ZNet instance = ZNet.instance; if (instance == null || !instance.IsServer() || peer.m_rpc != rpc || !peer.IsReady() || package.Size() <= 0 || package.Size() > 512 || !ConsumeEmojiPeerRequest(rpc)) { return; } package.SetPos(0); int num = package.ReadInt(); string text = package.ReadString(); string text2 = package.ReadString(); int num2 = package.ReadInt(); int num3 = package.ReadInt(); RequireEmojiPackageConsumed(package); if (num != 3 || !IsSha256(text) || !IsSha256(text2) || num2 < 0 || num2 % 65536 != 0 || num3 <= 0) { SendEmojiFileResponse(rpc, EmojiFileResponseStatus.Rejected, text, text2, 0, num2, num3, null); return; } ServerEmojiCatalog serverEmojiCatalog = _serverEmojiCatalog; if (serverEmojiCatalog == null || !StringComparer.Ordinal.Equals(serverEmojiCatalog.CatalogId, text)) { SendEmojiFileResponse(rpc, EmojiFileResponseStatus.StaleCatalog, text, text2, 0, num2, num3, null); return; } if (!serverEmojiCatalog.Files.TryGetValue(text2, out ServerEmojiBlob value) || num2 >= value.Data.Length) { SendEmojiFileResponse(rpc, EmojiFileResponseStatus.Unavailable, text, text2, 0, num2, num3, null); return; } int num4 = Math.Min(65536, value.Data.Length - num2); ISocket socket = rpc.GetSocket(); if (((socket != null) ? socket.GetSendQueueSize() : int.MaxValue) > 262144 - num4 || !ConsumeEmojiTransferBytes(rpc, num4)) { SendEmojiFileResponse(rpc, EmojiFileResponseStatus.Busy, text, text2, value.Data.Length, num2, num3, null); return; } byte[] array = new byte[num4]; Buffer.BlockCopy(value.Data, num2, array, 0, num4); SendEmojiFileResponse(rpc, EmojiFileResponseStatus.Data, text, text2, value.Data.Length, num2, num3, array); } catch (Exception ex) { if (ShouldLogMalformedEmojiRequest(rpc)) { ClanPlugin.ClanLogger.LogWarning((object)("Rejected malformed Clan media file request: " + ex.Message)); } } } private static void HandleEmojiFileResponse(ZRpc rpc, ZPackage package) { try { ZNet instance = ZNet.instance; if (rpc != ((instance != null) ? instance.GetServerRPC() : null) || package.Size() <= 0 || package.Size() > 66048) { return; } package.SetPos(0); int num = package.ReadInt(); EmojiFileResponseStatus emojiFileResponseStatus = (EmojiFileResponseStatus)package.ReadByte(); string text = package.ReadString(); string text2 = package.ReadString(); int num2 = package.ReadInt(); int num3 = package.ReadInt(); int num4 = package.ReadInt(); if (num != 3 || !Enum.IsDefined(typeof(EmojiFileResponseStatus), emojiFileResponseStatus) || !IsSha256(text) || !IsSha256(text2) || num2 < 0 || num3 < 0 || num3 % 65536 != 0 || num4 <= 0) { throw new InvalidDataException("Emoji response header is invalid."); } byte[] array = null; if (emojiFileResponseStatus == EmojiFileResponseStatus.Data) { int num5 = package.ReadInt(); int num6 = package.Size() - package.GetPos(); if (num5 <= 0 || num5 > 65536 || num6 != num5) { throw new InvalidDataException("Emoji response chunk length is invalid."); } array = package.ReadByteArray(num5); } RequireEmojiPackageConsumed(package); ClientEmojiCatalog clientEmojiCatalog = _clientEmojiCatalog; ClientEmojiDownload clientEmojiDownload = clientEmojiCatalog?.Download; if (clientEmojiCatalog == null || clientEmojiDownload == null || clientEmojiCatalog.Failed || clientEmojiCatalog.Session != ZNet.instance || !StringComparer.Ordinal.Equals(clientEmojiCatalog.CatalogId, text) || !StringComparer.Ordinal.Equals(clientEmojiDownload.Record.Hash, text2) || !clientEmojiDownload.TryGetChunkIndex(num3, out var chunkIndex) || !clientEmojiDownload.HasIssuedRequest(chunkIndex, num4) || clientEmojiDownload.IsChunkReceived(chunkIndex)) { return; } if ((emojiFileResponseStatus == EmojiFileResponseStatus.Data || emojiFileResponseStatus == EmojiFileResponseStatus.Busy) ? (num2 != clientEmojiDownload.Record.Length) : ((byte)num2 != 0)) { throw new InvalidDataException("Emoji response length is invalid for its status."); } switch (emojiFileResponseStatus) { case EmojiFileResponseStatus.Data: if (array == null || num2 != clientEmojiDownload.Record.Length || array.Length != Math.Min(65536, clientEmojiDownload.Record.Length - num3)) { throw new InvalidDataException("Emoji response does not match the manifest."); } Buffer.BlockCopy(array, 0, clientEmojiDownload.Buffer, num3, array.Length); clientEmojiDownload.MarkReceived(chunkIndex, array.Length); break; case EmojiFileResponseStatus.Busy: if (clientEmojiDownload.IsCurrentRequest(chunkIndex, num4)) { clientEmojiDownload.DeferChunk(chunkIndex, Time.realtimeSinceStartup + 0.1f); } break; case EmojiFileResponseStatus.StaleCatalog: if (clientEmojiDownload.IsCurrentRequest(chunkIndex, num4)) { FailEmojiClientCatalog(clientEmojiCatalog, "The server replaced the media catalog during download."); } break; case EmojiFileResponseStatus.Unavailable: case EmojiFileResponseStatus.Rejected: if (clientEmojiDownload.IsCurrentRequest(chunkIndex, num4)) { RetryEmojiChunk(clientEmojiCatalog, clientEmojiDownload, chunkIndex, emojiFileResponseStatus.ToString(), 0.25f); } break; } } catch (Exception ex) { ClientEmojiCatalog clientEmojiCatalog2 = _clientEmojiCatalog; if (clientEmojiCatalog2 != null) { FailEmojiClientCatalog(clientEmojiCatalog2, "Malformed Clan media response was rejected: " + ex.Message); } } } private static void SendEmojiFileResponse(ZRpc rpc, EmojiFileResponseStatus status, string catalogId, string hash, int totalLength, int offset, int requestId, byte[]? chunk) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(3); val.Write((byte)status); val.Write(catalogId); val.Write(hash); val.Write(totalLength); val.Write(offset); val.Write(requestId); if (status == EmojiFileResponseStatus.Data) { val.Write(chunk ?? throw new ArgumentNullException("chunk")); } rpc.Invoke(EmojiFileResponseRpc, new object[1] { val }); } private static bool ConsumeEmojiPeerRequest(ZRpc rpc) { return GetEmojiPeerBudget(rpc).TryConsumeRequest(Time.realtimeSinceStartup); } private static bool ConsumeEmojiTransferBytes(ZRpc rpc, int bytes) { if (bytes <= 0 || bytes > 65536) { return false; } float realtimeSinceStartup = Time.realtimeSinceStartup; PeerTransferBudget emojiPeerBudget = GetEmojiPeerBudget(rpc); emojiPeerBudget.Refill(realtimeSinceStartup); EmojiGlobalTransferBudget.Refill(realtimeSinceStartup); if (emojiPeerBudget.ByteTokens < (double)bytes || EmojiGlobalTransferBudget.ByteTokens < (double)bytes) { return false; } emojiPeerBudget.ByteTokens -= bytes; EmojiGlobalTransferBudget.ByteTokens -= bytes; return true; } private static bool ShouldLogMalformedEmojiRequest(ZRpc rpc) { return GetEmojiPeerBudget(rpc).TryConsumeMalformedLog(Time.realtimeSinceStartup); } private static PeerTransferBudget GetEmojiPeerBudget(ZRpc rpc) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!EmojiPeerBudgets.TryGetValue(rpc, out PeerTransferBudget value)) { value = new PeerTransferBudget(realtimeSinceStartup); EmojiPeerBudgets[rpc] = value; } return value; } internal static void ForgetEmojiPeer(ZRpc? rpc) { if (rpc != null) { EmojiPeerBudgets.Remove(rpc); } } private static void RetryEmojiChunk(ClientEmojiCatalog catalog, ClientEmojiDownload download, int chunkIndex, string reason, float delay) { int num = download.IncrementRetry(chunkIndex); if (num > 3) { FailEmojiClientCatalog(catalog, "Emoji '" + download.Record.Name + "' could not be downloaded: " + reason + "."); } else { download.DeferChunk(chunkIndex, Time.realtimeSinceStartup + delay * (float)(1 << num - 1)); } } private static void FailEmojiClientCatalog(ClientEmojiCatalog catalog, string message) { if (!catalog.Failed) { catalog.Failed = true; catalog.Download = null; if (catalog.CatalogRetryCount == 0) { catalog.CatalogRetryAt = Time.realtimeSinceStartup + 30f; } ClanPlugin.ClanLogger.LogWarning((object)(message + " The previous Clan media set remains active." + ((catalog.CatalogRetryCount == 0) ? " The catalog will be retried once." : ""))); } } private static byte[] ReadSyncedEmojiFile(ManifestRecord record) { if (!TryReadSyncedEmojiFile(record, out byte[] data)) { throw new FileNotFoundException("Synchronized file '" + record.Name + record.Extension + "' is not available."); } return data; } private static bool TryReadSyncedEmojiFile(ManifestRecord record, out byte[] data) { SyncedMediaFileSource source; return TryReadSyncedEmojiFile(record, out data, out source); } private static bool TryReadSyncedEmojiFile(ManifestRecord record, out byte[] data, out SyncedMediaFileSource source) { ZNet instance = ZNet.instance; ServerEmojiCatalog serverEmojiCatalog = _serverEmojiCatalog; if (instance != null && instance.IsServer() && serverEmojiCatalog != null && serverEmojiCatalog.Files.TryGetValue(record.Hash, out ServerEmojiBlob value) && value.Data.Length == record.Length && StringComparer.Ordinal.Equals(value.Record.Extension, record.Extension)) { data = value.Data; source = SyncedMediaFileSource.Server; return true; } if (TryReadEmojiCacheFile(record, out data)) { source = SyncedMediaFileSource.Cache; return true; } if ((Object)(object)instance != (Object)null && !instance.IsServer() && TryReadConfiguredMediaFile(record, out data)) { source = SyncedMediaFileSource.Config; return true; } source = SyncedMediaFileSource.None; return false; } private static bool TryReadConfiguredMediaFile(ManifestRecord record, out byte[] data) { string path; switch (record.Role) { case MediaRole.Emoji: path = EmojiDirectory; break; case MediaRole.Emblem: path = EmblemDirectory; break; default: data = Array.Empty(); return false; } string path2 = Path.Combine(path, record.Name + record.Extension); return TryReadVerifiedMediaFile(record, path2, out data); } private static bool TryReadVerifiedMediaFile(ManifestRecord record, string path, out byte[] data) { try { FileInfo fileInfo = new FileInfo(path); if (!fileInfo.Exists || fileInfo.Length != record.Length) { data = Array.Empty(); return false; } data = ReadStableFile(path, record.Length); if (!StringComparer.Ordinal.Equals(ComputeSha256(data), record.Hash)) { data = Array.Empty(); return false; } return true; } catch { data = Array.Empty(); return false; } } private static bool TryReadEmojiCacheFile(ManifestRecord record, out byte[] data) { string path = EmojiCachePath(record); if (!TryReadVerifiedMediaFile(record, path, out data)) { return false; } try { File.SetLastWriteTimeUtc(path, DateTime.UtcNow); } catch { } return true; } private static void WriteEmojiCacheAtomically(ManifestRecord record, byte[] data) { if (data.Length != record.Length || !StringComparer.Ordinal.Equals(ComputeSha256(data), record.Hash)) { throw new InvalidDataException("Cache data for '" + record.Name + record.Extension + "' does not match its manifest."); } Directory.CreateDirectory(EmojiCacheDirectory); string text = EmojiCachePath(record); if (TryReadEmojiCacheFile(record, out byte[] _)) { return; } string text2 = text + "." + Guid.NewGuid().ToString("N") + ".part"; try { using (FileStream fileStream = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(data, 0, data.Length); fileStream.Flush(flushToDisk: true); } PublishEmojiCacheFile(record, text2, text); } finally { TryDeleteFile(text2); } } private static void PublishEmojiCacheFile(ManifestRecord record, string temporary, string destination) { byte[] data; try { File.Move(temporary, destination); return; } catch (IOException) { if (TryReadEmojiCacheFile(record, out data)) { return; } } try { File.Replace(temporary, destination, null); } catch (IOException) { if (TryReadEmojiCacheFile(record, out data)) { return; } if (File.Exists(destination)) { throw; } try { File.Move(temporary, destination); } catch (IOException) when (TryReadEmojiCacheFile(record, out data)) { } } } private static string EmojiCachePath(ManifestRecord record) { return Path.Combine(EmojiCacheDirectory, record.Hash + record.Extension); } private static void PruneEmojiCache(IReadOnlyList activeRecords) { FileStream fileStream = null; try { DirectoryInfo directoryInfo = new DirectoryInfo(EmojiCacheDirectory); if (!directoryInfo.Exists) { return; } try { fileStream = new FileStream(Path.Combine(EmojiCacheDirectory, ".prune.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } catch (IOException) { return; } List source = (from file in directoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly) where StringComparer.Ordinal.Equals(file.Extension, ".png") || StringComparer.Ordinal.Equals(file.Extension, ".gif") select file).ToList(); long num = source.Sum((FileInfo file) => file.Length); if (num <= 402653184) { return; } HashSet active = (from record in _activeRecords.Concat(activeRecords) select record.Hash + record.Extension).ToHashSet(StringComparer.Ordinal); foreach (FileInfo item in from file in source where !active.Contains(file.Name) orderby file.LastWriteTimeUtc select file) { long length = item.Length; TryDeleteFile(item.FullName); if (!File.Exists(item.FullName)) { num -= length; } if (num <= 335544320) { break; } } } catch (Exception ex2) { ClanPlugin.ClanLogger.LogWarning((object)("Clan media cache cleanup failed: " + ex2.Message)); } finally { fileStream?.Dispose(); } } private static void DeletePartialEmojiCacheFiles() { if (!Directory.Exists(EmojiCacheDirectory)) { return; } long num = DateTime.UtcNow.Ticks - 864000000000L; foreach (string item in Directory.EnumerateFiles(EmojiCacheDirectory, "*.part", SearchOption.TopDirectoryOnly)) { try { if (File.GetLastWriteTimeUtc(item).Ticks <= num) { TryDeleteFile(item); } } catch { } } } private static void TryDeleteFile(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } private static void CreateEmojiFileWatcher() { FileSystemWatcher fileSystemWatcher = null; FileSystemWatcher fileSystemWatcher2 = null; try { Directory.CreateDirectory(EmojiDirectory); Directory.CreateDirectory(EmblemDirectory); fileSystemWatcher = CreateMediaFileWatcher(EmojiDirectory); fileSystemWatcher2 = CreateMediaFileWatcher(EmblemDirectory); _emojiFileWatcher = fileSystemWatcher; _emblemFileWatcher = fileSystemWatcher2; Interlocked.Exchange(ref _emojiWatcherRestartFailures, 0); } catch (Exception ex) { DisposeMediaFileWatcher(fileSystemWatcher); DisposeMediaFileWatcher(fileSystemWatcher2); _emojiFileWatcher = null; _emblemFileWatcher = null; ClanPlugin.ClanLogger.LogWarning((object)("Clan media hot reload watchers could not be started: " + ex.Message)); if (Interlocked.Increment(ref _emojiWatcherRestartFailures) <= 3) { Interlocked.Exchange(ref _emojiWatcherNeedsRestart, 1); Interlocked.Exchange(ref _emojiReloadRequestedAtTicks, DateTime.UtcNow.Ticks); } } } private static FileSystemWatcher CreateMediaFileWatcher(string directory) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(directory); fileSystemWatcher.IncludeSubdirectories = false; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime; fileSystemWatcher.Changed += OnEmojiFileChanged; fileSystemWatcher.Created += OnEmojiFileChanged; fileSystemWatcher.Deleted += OnEmojiFileChanged; fileSystemWatcher.Renamed += OnEmojiFileRenamed; fileSystemWatcher.Error += OnEmojiWatcherError; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } private static void DisposeEmojiFileWatcher() { FileSystemWatcher emojiFileWatcher = _emojiFileWatcher; FileSystemWatcher? emblemFileWatcher = _emblemFileWatcher; _emojiFileWatcher = null; _emblemFileWatcher = null; DisposeMediaFileWatcher(emojiFileWatcher); DisposeMediaFileWatcher(emblemFileWatcher); } private static void DisposeMediaFileWatcher(FileSystemWatcher? watcher) { if (watcher != null) { watcher.EnableRaisingEvents = false; watcher.Changed -= OnEmojiFileChanged; watcher.Created -= OnEmojiFileChanged; watcher.Deleted -= OnEmojiFileChanged; watcher.Renamed -= OnEmojiFileRenamed; watcher.Error -= OnEmojiWatcherError; watcher.Dispose(); } } private static void OnEmojiFileChanged(object sender, FileSystemEventArgs args) { if (IsSupportedEmojiPath(args.FullPath)) { MarkEmojiServerFileDirty(args.FullPath); } } private static void OnEmojiFileRenamed(object sender, RenamedEventArgs args) { if (IsSupportedEmojiPath(args.OldFullPath)) { MarkEmojiServerFileDirty(args.OldFullPath); } if (IsSupportedEmojiPath(args.FullPath)) { MarkEmojiServerFileDirty(args.FullPath); } } private static void OnEmojiWatcherError(object sender, ErrorEventArgs args) { Interlocked.Exchange(ref _emojiWatcherRestartFailures, 0); Interlocked.Exchange(ref _emojiWatcherNeedsRestart, 1); MarkAllEmojiServerFilesDirty(); } private static void MarkAllEmojiServerFilesDirty() { try { string[] array = new string[2] { EmojiDirectory, EmblemDirectory }; foreach (string path in array) { if (!Directory.Exists(path)) { continue; } foreach (string item in Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly)) { if (IsSupportedEmojiPath(item)) { MarkEmojiServerFileDirty(item); } } } } catch { } MarkEmojiServerFilesDirty(); } private static bool IsSupportedEmojiPath(string path) { string extension = Path.GetExtension(path); if (!StringComparer.Ordinal.Equals(extension, ".png")) { return StringComparer.Ordinal.Equals(extension, ".gif"); } return true; } private static void MarkEmojiServerFilesDirty() { Interlocked.Exchange(ref _emojiReloadFailures, 0); Interlocked.Exchange(ref _emojiReloadRequestedAtTicks, DateTime.UtcNow.Ticks); } private static bool ConsumeEmojiServerReloadRequest() { long num = Interlocked.Read(in _emojiReloadRequestedAtTicks); if (num == 0L || DateTime.UtcNow.Ticks - num < 7500000) { return false; } if (Interlocked.CompareExchange(ref _emojiReloadRequestedAtTicks, 0L, num) != num) { return false; } if (Interlocked.Exchange(ref _emojiWatcherNeedsRestart, 0) != 0) { DisposeEmojiFileWatcher(); CreateEmojiFileWatcher(); } return true; } private static void ResetEmojiServerReloadFailures() { Interlocked.Exchange(ref _emojiReloadFailures, 0); } private static void ScheduleEmojiServerReloadRetry(Exception error, bool retryAnyError = false) { if ((retryAnyError || error is IOException || error is InvalidDataException) && Interlocked.Increment(ref _emojiReloadFailures) <= 3) { Interlocked.Exchange(ref _emojiReloadRequestedAtTicks, DateTime.UtcNow.Ticks); } } private static void RequireEmojiPackageConsumed(ZPackage package) { if (package.GetPos() != package.Size()) { throw new InvalidDataException("Emoji RPC contains unexpected trailing data."); } } } internal static class ClanUiFactory { private const float ScrollSensitivity = 196f; private const float UnderfilledScrollSensitivity = 35f; private const float UnderfilledScrollElasticity = 0.07f; private const float RectComparisonTolerance = 0.25f; private static readonly Color FixedClanColor = new Color(0.48f, 0.92f, 0.78f, 1f); private static readonly Vector3[] WorldCorners = (Vector3[])(object)new Vector3[4]; public static Color GetClanColor() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return FixedClanColor; } public static GameObject CreateObject(string name, Transform parent, params Type[] components) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown Type[] array = new Type[components.Length + 1]; array[0] = typeof(RectTransform); Array.Copy(components, 0, array, 1, components.Length); GameObject val = new GameObject(name, array); val.transform.SetParent(parent, false); val.layer = 5; return val; } public static void ClearChildren(Transform parent) { for (int num = parent.childCount - 1; num >= 0; num--) { GameObject gameObject = ((Component)parent.GetChild(num)).gameObject; gameObject.SetActive(false); Object.Destroy((Object)(object)gameObject); } } public static Font GetBoldFont() { try { return GUIManager.Instance.AveriaSerifBold; } catch (Exception) { return Resources.GetBuiltinResource("Arial.ttf"); } } public static Camera? GetCanvasCamera(Component? component) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) Canvas val = (((Object)(object)component == (Object)null) ? null : component.GetComponentInParent()); if (!((Object)(object)val == (Object)null) && (int)val.renderMode != 0) { return val.worldCamera; } return null; } public static Rect GetScreenBounds(RectTransform rect, Camera? camera) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) rect.GetWorldCorners(WorldCorners); Vector2 val = RectTransformUtility.WorldToScreenPoint(camera, WorldCorners[0]); float num = val.x; float num2 = val.x; float num3 = val.y; float num4 = val.y; for (int i = 1; i < WorldCorners.Length; i++) { Vector2 val2 = RectTransformUtility.WorldToScreenPoint(camera, WorldCorners[i]); num = Mathf.Min(num, val2.x); num2 = Mathf.Max(num2, val2.x); num3 = Mathf.Min(num3, val2.y); num4 = Mathf.Max(num4, val2.y); } return Rect.MinMaxRect(num, num3, num2, num4); } public static bool RectApproximately(Rect left, Rect right) { if (Mathf.Abs(((Rect)(ref left)).xMin - ((Rect)(ref right)).xMin) <= 0.25f && Mathf.Abs(((Rect)(ref left)).yMin - ((Rect)(ref right)).yMin) <= 0.25f && Mathf.Abs(((Rect)(ref left)).xMax - ((Rect)(ref right)).xMax) <= 0.25f) { return Mathf.Abs(((Rect)(ref left)).yMax - ((Rect)(ref right)).yMax) <= 0.25f; } return false; } public static string CleanSingleLine(string? value) { return (value ?? "").Replace('\r', ' ').Replace('\n', ' ').Replace('<', ' ') .Replace('>', ' ') .Trim(); } public static void ConfigureVerticalScroll(ScrollRect scroll, bool hasOverflow, RectTransform? resetContentOnTransition = null) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) MovementType val = (MovementType)((!hasOverflow) ? 1 : 2); bool num = scroll.movementType != val; scroll.movementType = val; scroll.scrollSensitivity = (hasOverflow ? 196f : 35f); scroll.elasticity = 0.07f; if (num && !((Object)(object)resetContentOnTransition == (Object)null)) { scroll.StopMovement(); if (!hasOverflow) { resetContentOnTransition.anchoredPosition = Vector2.zero; } } } } internal enum ClanActionIcon { Edit, Folder, Resize, Collapse, Expand, Accept, Decline } internal enum ClanTooltipPlacement { Default, LeftOfTarget } internal static class ClanUiFeedback { private sealed class IconAsset { internal Texture2D Texture { get; } internal Sprite Sprite { get; } internal IconAsset(Texture2D texture, Sprite sprite) { Texture = texture; Sprite = sprite; } } private sealed class TooltipTrigger : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler { private Selectable? _target; private string _value = ""; private bool _scheduled; private bool _shown; private bool _pointerInside; private bool _usePointerPosition; private bool _richText; private ClanTooltipPlacement _placement; private Vector2 _pointerPosition; private float _showAt; internal void Configure(Selectable target, string value, bool richText, ClanTooltipPlacement placement) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) string text = value?.Trim() ?? ""; if ((Object)(object)_target == (Object)(object)target && _value == text && _richText == richText && _placement == placement) { ((Behaviour)this).enabled = text.Length > 0; return; } bool num = (Object)(object)_target == (Object)(object)target && _pointerInside && text.Length > 0; Cancel(); _target = target; _value = text; _richText = richText; _placement = placement; ((Behaviour)this).enabled = _value.Length > 0; if (num) { _usePointerPosition = true; _pointerPosition = Vector2.op_Implicit(Input.mousePosition); _shown = ShowTooltip(this, target, _value, usePointerPosition: true, _pointerPosition, _richText, _placement); } } public void OnPointerEnter(PointerEventData eventData) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) _pointerInside = true; Schedule(usePointerPosition: true, eventData.position); } public void OnPointerExit(PointerEventData eventData) { _pointerInside = false; Cancel(); } public void OnSelect(BaseEventData eventData) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) Schedule(usePointerPosition: false, Vector2.zero); } public void OnDeselect(BaseEventData eventData) { Cancel(); } private void Update() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (_scheduled && Time.unscaledTime >= _showAt) { _scheduled = false; _shown = (Object)(object)_target != (Object)null && ShowTooltip(this, _target, _value, _usePointerPosition, _pointerPosition, _richText, _placement); } else if (_shown && IsTooltipRootMissingFor(this) && (Object)(object)_target != (Object)null) { _shown = ShowTooltip(this, _target, _value, _usePointerPosition, _pointerPosition, _richText, _placement); } } private void OnDisable() { _pointerInside = false; Cancel(); } private void OnDestroy() { _pointerInside = false; Cancel(); } internal void NotifyTooltipHidden() { _shown = false; } private void Schedule(bool usePointerPosition, Vector2 pointerPosition) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (((Behaviour)this).enabled && !((Object)(object)_target == (Object)null) && !string.IsNullOrWhiteSpace(_value)) { HideTooltip(this); _usePointerPosition = usePointerPosition; _pointerPosition = pointerPosition; _showAt = Time.unscaledTime + 0.2f; _scheduled = true; _shown = false; } } private void Cancel() { _scheduled = false; _shown = false; HideTooltip(this); } } private const int IconPixels = 32; private const float TooltipDelaySeconds = 0.2f; private const float TooltipScreenGap = 6f; private const float TooltipPointerOffset = 14f; private const float TooltipHorizontalPadding = 9f; private const float TooltipVerticalPadding = 5f; private const float TooltipMaximumTextWidth = 320f; private const float TooltipMaximumHeight = 160f; private const float NotificationPulsePeriodSeconds = 1f; private const string IconObjectName = "ClanActionIcon"; private static readonly Dictionary IconAssets = new Dictionary(); private static readonly Vector3[] TooltipWorldCorners = (Vector3[])(object)new Vector3[4]; private static GameObject? _tooltipRoot; private static RectTransform? _tooltipRect; private static Text? _tooltipText; private static Transform? _tooltipHost; private static Canvas? _tooltipCanvas; private static TooltipTrigger? _tooltipOwner; internal static Color GetNotificationPulseColor() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Repeat(Time.unscaledTime, 1f) / 1f; float num2 = 0.5f - 0.5f * Mathf.Cos(num * (float)Math.PI * 2f); return Color.Lerp(Color.white, ClanUiFactory.GetClanColor(), num2); } internal static Image ApplyIcon(Button button, ClanActionIcon icon, string tooltip, float displaySize = 32f) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)button == (Object)null) { throw new ArgumentNullException("button"); } Text[] componentsInChildren = ((Component)button).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].text = ""; } TMP_Text[] componentsInChildren2 = ((Component)button).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].text = ""; } Transform val = ((Component)button).transform.Find("ClanActionIcon"); GameObject val2; if ((Object)(object)val != (Object)null) { val2 = ((Component)val).gameObject; } else { val2 = new GameObject("ClanActionIcon", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val2.transform.SetParent(((Component)button).transform, false); } val2.layer = ((Component)button).gameObject.layer; val2.SetActive(true); val2.transform.SetAsLastSibling(); RectTransform component = val2.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.anchoredPosition = Vector2.zero; float num = Mathf.Max(1f, displaySize); component.sizeDelta = new Vector2(num, num); Image component2 = val2.GetComponent(); component2.sprite = GetIcon(icon); ((Graphic)component2).color = Color.white; component2.preserveAspect = true; ((Graphic)component2).raycastTarget = false; SetTooltip((Selectable)(object)button, tooltip); return component2; } internal static void SetTooltip(Selectable selectable, string tooltip, bool richText = false, ClanTooltipPlacement placement = ClanTooltipPlacement.Default) { if ((Object)(object)selectable == (Object)null) { throw new ArgumentNullException("selectable"); } (((Component)selectable).GetComponent() ?? ((Component)selectable).gameObject.AddComponent()).Configure(selectable, tooltip, richText, placement); } internal static Sprite GetIcon(ClanActionIcon icon) { if (IconAssets.TryGetValue(icon, out IconAsset value) && (Object)(object)value.Texture != (Object)null && (Object)(object)value.Sprite != (Object)null) { return value.Sprite; } if (value != null) { DestroyObject((Object?)(object)value.Sprite); DestroyObject((Object?)(object)value.Texture); IconAssets.Remove(icon); } IconAsset iconAsset = CreateIcon(icon); IconAssets.Add(icon, iconAsset); return iconAsset.Sprite; } internal static void HideTooltip() { _tooltipOwner?.NotifyTooltipHidden(); _tooltipOwner = null; if ((Object)(object)_tooltipRoot != (Object)null) { _tooltipRoot.SetActive(false); } } internal static void Dispose() { HideTooltip(); if ((Object)(object)_tooltipRoot != (Object)null) { GameObject? tooltipRoot = _tooltipRoot; ClearTooltipReferences(); DestroyObject((Object?)(object)tooltipRoot); } else { ClearTooltipReferences(); } foreach (IconAsset value in IconAssets.Values) { DestroyObject((Object?)(object)value.Sprite); DestroyObject((Object?)(object)value.Texture); } IconAssets.Clear(); } private static bool ShowTooltip(TooltipTrigger owner, Selectable target, string value, bool usePointerPosition, Vector2 pointerPosition, bool richText, ClanTooltipPlacement placement) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null || string.IsNullOrWhiteSpace(value) || !EnsureTooltip(target)) { return false; } if ((Object)(object)_tooltipOwner != (Object)(object)owner) { _tooltipOwner?.NotifyTooltipHidden(); _tooltipOwner = owner; } _tooltipText.supportRichText = richText; _tooltipText.text = value.Trim(); SizeTooltip(); _tooltipRoot.SetActive(true); _tooltipRoot.transform.SetAsLastSibling(); if (placement == ClanTooltipPlacement.LeftOfTarget && TryPlaceTooltipBesideTarget(target)) { return true; } _tooltipRect.pivot = new Vector2(0f, 1f); PlaceAndClampTooltip(usePointerPosition ? (pointerPosition + new Vector2(14f, -14f)) : GetSelectionAnchor(target)); return true; } private static bool TryPlaceTooltipBesideTarget(Selectable target) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)target).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val == null || (Object)(object)_tooltipRect == (Object)null) { return false; } val.GetWorldCorners(TooltipWorldCorners); Camera? canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)((Component)target).GetComponentInParent()); Vector2 val2 = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[0]); Vector2 val3 = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[1]); Vector2 val4 = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[2]); Vector2 val5 = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[3]); Vector2 val6 = (val2 + val3) * 0.5f; Vector2 val7 = (val5 + val4) * 0.5f; _tooltipRect.pivot = new Vector2(1f, 0.5f); Vector2 val8 = val6 + Vector2.left * 14f; SetTooltipScreenPosition(val8); Canvas.ForceUpdateCanvases(); if (IsTooltipInsideHorizontalSafeArea()) { PlaceAndClampTooltip(val8); return true; } _tooltipRect.pivot = new Vector2(0f, 0.5f); PlaceAndClampTooltip(val7 + Vector2.right * 14f); return true; } private static bool IsTooltipInsideHorizontalSafeArea() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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) _tooltipRect.GetWorldCorners(TooltipWorldCorners); Camera? canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)_tooltipCanvas); Vector2 val = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[0]); Vector2 val2 = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[2]); float num = Mathf.Min(val.x, val2.x); float num2 = Mathf.Max(val.x, val2.x); Rect safeArea = GetSafeArea(); if (num >= ((Rect)(ref safeArea)).xMin + 6f) { return num2 <= ((Rect)(ref safeArea)).xMax - 6f; } return false; } private static void HideTooltip(TooltipTrigger owner) { if (!((Object)(object)_tooltipOwner != (Object)(object)owner)) { _tooltipOwner = null; if ((Object)(object)_tooltipRoot != (Object)null) { _tooltipRoot.SetActive(false); } } } private static bool IsTooltipRootMissingFor(TooltipTrigger owner) { if ((Object)(object)_tooltipOwner == (Object)(object)owner) { if (!((Object)(object)_tooltipRoot == (Object)null) && !((Object)(object)_tooltipRect == (Object)null)) { return (Object)(object)_tooltipText == (Object)null; } return true; } return false; } private static bool EnsureTooltip(Selectable target) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) if (!TryResolveTooltipHost(target, out Transform host, out Canvas canvas)) { return false; } if ((Object)(object)_tooltipRoot != (Object)null && (Object)(object)_tooltipRect != (Object)null && (Object)(object)_tooltipText != (Object)null && (Object)(object)_tooltipHost == (Object)(object)host && (Object)(object)_tooltipCanvas == (Object)(object)canvas) { return true; } if ((Object)(object)_tooltipRoot != (Object)null) { GameObject? tooltipRoot = _tooltipRoot; ClearTooltipReferences(); tooltipRoot.SetActive(false); DestroyObject((Object?)(object)tooltipRoot); } GameObject val = new GameObject("ClanTooltip", new Type[5] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup), typeof(Outline) }); val.transform.SetParent(host, false); val.layer = ((Component)host).gameObject.layer; RectTransform component = val.GetComponent(); RectTransform val2 = (RectTransform)host; component.anchorMin = val2.pivot; component.anchorMax = val2.pivot; component.pivot = new Vector2(0f, 1f); component.anchoredPosition = Vector2.zero; component.sizeDelta = new Vector2(80f, 28f); Image component2 = val.GetComponent(); ((Graphic)component2).color = new Color(0.045f, 0.04f, 0.035f, 0.96f); ((Graphic)component2).raycastTarget = false; Outline component3 = val.GetComponent(); ((Shadow)component3).effectColor = new Color(0.72f, 0.58f, 0.34f, 0.9f); ((Shadow)component3).effectDistance = new Vector2(1f, -1f); ((Shadow)component3).useGraphicAlpha = true; CanvasGroup component4 = val.GetComponent(); component4.alpha = 1f; component4.interactable = false; component4.blocksRaycasts = false; component4.ignoreParentGroups = false; GameObject val3 = new GameObject("Text", new Type[4] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Text), typeof(Shadow) }); val3.transform.SetParent(val.transform, false); val3.layer = val.layer; Text component5 = val3.GetComponent(); component5.font = ClanUiFactory.GetBoldFont(); component5.fontSize = 14; component5.fontStyle = (FontStyle)0; component5.alignment = (TextAnchor)3; ((Graphic)component5).color = new Color(0.96f, 0.92f, 0.82f, 1f); component5.horizontalOverflow = (HorizontalWrapMode)0; component5.verticalOverflow = (VerticalWrapMode)0; component5.supportRichText = false; ((Graphic)component5).raycastTarget = false; Shadow component6 = val3.GetComponent(); component6.effectColor = new Color(0f, 0f, 0f, 0.75f); component6.effectDistance = new Vector2(1f, -1f); component6.useGraphicAlpha = true; RectTransform component7 = val3.GetComponent(); component7.anchorMin = Vector2.zero; component7.anchorMax = Vector2.one; component7.offsetMin = new Vector2(9f, 5f); component7.offsetMax = new Vector2(-9f, -5f); val.SetActive(false); _tooltipRoot = val; _tooltipRect = component; _tooltipText = component5; _tooltipHost = host; _tooltipCanvas = canvas; return true; } private static bool TryResolveTooltipHost(Selectable target, out Transform host, out Canvas canvas) { try { GameObject customGUIFront = GUIManager.CustomGUIFront; Canvas val = (((Object)(object)customGUIFront != (Object)null) ? customGUIFront.GetComponentInParent() : null); if ((Object)(object)customGUIFront != (Object)null && customGUIFront.activeInHierarchy && customGUIFront.transform is RectTransform && (Object)(object)val != (Object)null && ((Behaviour)val).isActiveAndEnabled) { host = customGUIFront.transform; canvas = val; return true; } } catch (Exception) { } Canvas componentInParent = ((Component)target).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && ((Component)componentInParent).transform is RectTransform) { host = ((Component)componentInParent).transform; canvas = componentInParent; return true; } host = null; canvas = null; return false; } private static void ClearTooltipReferences() { _tooltipRoot = null; _tooltipRect = null; _tooltipText = null; _tooltipHost = null; _tooltipCanvas = null; } private static void SizeTooltip() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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_0163: Unknown result type (might be due to invalid IL or missing references) Rect safeArea = GetSafeArea(); float num = Mathf.Max(0.01f, _tooltipCanvas.scaleFactor); float num2 = Mathf.Max(24f, (((Rect)(ref safeArea)).width - 12f) / num - 18f); float num3 = Mathf.Min(320f, num2); TextGenerationSettings generationSettings = _tooltipText.GetGenerationSettings(new Vector2(num3, 0f)); float num4 = Mathf.Max(0.01f, _tooltipText.pixelsPerUnit); float num5 = _tooltipText.cachedTextGeneratorForLayout.GetPreferredWidth(_tooltipText.text, generationSettings) / num4; if (!IsFinitePositive(num5)) { num5 = 80f; } float num6 = Mathf.Clamp(num5, 24f, num3); TextGenerationSettings generationSettings2 = _tooltipText.GetGenerationSettings(new Vector2(num6, 0f)); float num7 = _tooltipText.cachedTextGeneratorForLayout.GetPreferredHeight(_tooltipText.text, generationSettings2) / num4; if (!IsFinitePositive(num7)) { num7 = (float)_tooltipText.fontSize * 1.4f; } float num8 = Mathf.Max(20f, (((Rect)(ref safeArea)).height - 12f) / num - 10f); float num9 = Mathf.Clamp(num7, (float)_tooltipText.fontSize * 1.2f, Mathf.Min(160f, num8)); _tooltipRect.sizeDelta = new Vector2(num6 + 18f, num9 + 10f); } private static Vector2 GetSelectionAnchor(Selectable target) { //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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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) Transform transform = ((Component)target).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)val == (Object)null) { return new Vector2((float)Screen.width * 0.5f, (float)Screen.height * 0.5f); } Vector3[] array = (Vector3[])(object)new Vector3[4]; val.GetWorldCorners(array); return RectTransformUtility.WorldToScreenPoint(ClanUiFactory.GetCanvasCamera((Component?)(object)((Component)target).GetComponentInParent()), array[2]) + new Vector2(14f, -4f); } private static void PlaceAndClampTooltip(Vector2 screenPosition) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_003c: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_0091: 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_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) SetTooltipScreenPosition(screenPosition); Canvas.ForceUpdateCanvases(); _tooltipRect.GetWorldCorners(TooltipWorldCorners); Camera? canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)_tooltipCanvas); Vector2 val = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[0]); Vector2 val2 = RectTransformUtility.WorldToScreenPoint(canvasCamera, TooltipWorldCorners[2]); float num = Mathf.Min(val.x, val2.x); float num2 = Mathf.Max(val.x, val2.x); float num3 = Mathf.Min(val.y, val2.y); float num4 = Mathf.Max(val.y, val2.y); Rect safeArea = GetSafeArea(); float num5 = ((Rect)(ref safeArea)).xMin + 6f; float num6 = ((Rect)(ref safeArea)).xMax - 6f; float num7 = ((Rect)(ref safeArea)).yMin + 6f; float num8 = ((Rect)(ref safeArea)).yMax - 6f; Vector2 zero = Vector2.zero; if (num < num5) { zero.x += num5 - num; } if (num2 + zero.x > num6) { zero.x += num6 - (num2 + zero.x); } if (num3 < num7) { zero.y += num7 - num3; } if (num4 + zero.y > num8) { zero.y += num8 - (num4 + zero.y); } if (((Vector2)(ref zero)).sqrMagnitude > 0.01f) { SetTooltipScreenPosition(screenPosition + zero); } } private static void SetTooltipScreenPosition(Vector2 screenPosition) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) Transform? tooltipHost = _tooltipHost; RectTransform val = (RectTransform)(object)((tooltipHost is RectTransform) ? tooltipHost : null); if (val != null) { Camera canvasCamera = ClanUiFactory.GetCanvasCamera((Component?)(object)_tooltipCanvas); Vector2 anchoredPosition = default(Vector2); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(val, screenPosition, canvasCamera, ref anchoredPosition)) { _tooltipRect.anchoredPosition = anchoredPosition; } } } private static Rect GetSafeArea() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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) Rect safeArea = Screen.safeArea; if (!(((Rect)(ref safeArea)).width > 0f) || !(((Rect)(ref safeArea)).height > 0f)) { return new Rect(0f, 0f, (float)Screen.width, (float)Screen.height); } return safeArea; } private static bool IsFinitePositive(float value) { if (value > 0f && !float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static IconAsset CreateIcon(ClanActionIcon icon) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00af: 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_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) //IL_00e6: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Expected O, but got Unknown //IL_0300: Expected O, but got Unknown Color32[] array = (Color32[])(object)new Color32[1024]; Color32 val = default(Color32); ((Color32)(ref val))..ctor((byte)18, (byte)16, (byte)14, (byte)235); Color32 val2 = (Color32)(icon switch { ClanActionIcon.Edit => new Color32(byte.MaxValue, (byte)207, (byte)96, byte.MaxValue), ClanActionIcon.Folder => new Color32(byte.MaxValue, (byte)207, (byte)96, byte.MaxValue), ClanActionIcon.Resize => new Color32((byte)248, (byte)248, (byte)244, byte.MaxValue), ClanActionIcon.Collapse => new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), ClanActionIcon.Expand => new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), ClanActionIcon.Accept => new Color32((byte)104, (byte)235, (byte)150, byte.MaxValue), ClanActionIcon.Decline => new Color32(byte.MaxValue, (byte)112, (byte)108, byte.MaxValue), _ => throw new ArgumentOutOfRangeException("icon", icon, null), }); switch (icon) { case ClanActionIcon.Edit: DrawLine(array, new Vector2(7.5f, 7.5f), new Vector2(23f, 23f), 8f, val); DrawLine(array, new Vector2(7.5f, 7.5f), new Vector2(23f, 23f), 4.8f, val2); DrawLine(array, new Vector2(20.5f, 25.5f), new Vector2(25.5f, 20.5f), 4.5f, val); DrawLine(array, new Vector2(20.5f, 25.5f), new Vector2(25.5f, 20.5f), 2f, val2); DrawLine(array, new Vector2(5.5f, 5.5f), new Vector2(9f, 6.5f), 3f, val); break; case ClanActionIcon.Folder: DrawFolderIcon(array, val, val2); break; case ClanActionIcon.Resize: DrawResizeIcon(array, 6f, val); DrawResizeIcon(array, 3f, val2); break; case ClanActionIcon.Collapse: DrawTriangle(array, pointsUp: true, val2); break; case ClanActionIcon.Expand: DrawTriangle(array, pointsUp: false, val2); break; case ClanActionIcon.Accept: DrawCheckIcon(array, val, val2); break; case ClanActionIcon.Decline: DrawCrossIcon(array, val, val2); break; } Texture2D val3 = new Texture2D(32, 32, (TextureFormat)4, false) { name = $"ClanActionIcon.{icon}.Texture", filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; val3.SetPixels32(array); val3.Apply(false, true); Sprite val4 = Sprite.Create(val3, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f), 32f, 0u, (SpriteMeshType)0); ((Object)val4).name = $"ClanActionIcon.{icon}"; ((Object)val4).hideFlags = (HideFlags)61; return new IconAsset(val3, val4); } private static void DrawFolderIcon(Color32[] pixels, Color32 outline, Color32 foreground) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) Vector2[] array = (Vector2[])(object)new Vector2[7] { new Vector2(6f, 7f), new Vector2(26f, 7f), new Vector2(26f, 21f), new Vector2(16f, 21f), new Vector2(13f, 25f), new Vector2(6f, 25f), new Vector2(6f, 7f) }; for (int i = 1; i < array.Length; i++) { DrawLine(pixels, array[i - 1], array[i], 6f, outline); } for (int j = 1; j < array.Length; j++) { DrawLine(pixels, array[j - 1], array[j], 3f, foreground); } } private static void DrawResizeIcon(Color32[] pixels, float width, Color32 color) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0038: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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) Vector2 start = default(Vector2); ((Vector2)(ref start))..ctor(7.5f, 24.5f); Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(24.5f, 7.5f); DrawLine(pixels, start, val, width, color); DrawLine(pixels, start, new Vector2(7.5f, 16.5f), width, color); DrawLine(pixels, start, new Vector2(15.5f, 24.5f), width, color); DrawLine(pixels, val, new Vector2(24.5f, 15.5f), width, color); DrawLine(pixels, val, new Vector2(16.5f, 7.5f), width, color); } private static void DrawCheckIcon(Color32[] pixels, Color32 outline, Color32 foreground) { //IL_0034: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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) Vector2 start = default(Vector2); ((Vector2)(ref start))..ctor(6.5f, 16f); Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(13f, 9.5f); Vector2 end = default(Vector2); ((Vector2)(ref end))..ctor(26f, 23f); DrawLine(pixels, start, val, 7f, outline); DrawLine(pixels, val, end, 7f, outline); DrawLine(pixels, start, val, 4f, foreground); DrawLine(pixels, val, end, 4f, foreground); } private static void DrawCrossIcon(Color32[] pixels, Color32 outline, Color32 foreground) { //IL_0045: 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_004c: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) Vector2 start = default(Vector2); ((Vector2)(ref start))..ctor(8f, 8f); Vector2 start2 = default(Vector2); ((Vector2)(ref start2))..ctor(8f, 24f); Vector2 end = default(Vector2); ((Vector2)(ref end))..ctor(24f, 8f); Vector2 end2 = default(Vector2); ((Vector2)(ref end2))..ctor(24f, 24f); DrawLine(pixels, start, end2, 7f, outline); DrawLine(pixels, start2, end, 7f, outline); DrawLine(pixels, start, end2, 4f, foreground); DrawLine(pixels, start2, end, 4f, foreground); } private static void DrawTriangle(Color32[] pixels, bool pointsUp, Color32 color) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) int num = 14; for (int i = 9; i <= 23; i++) { float num2 = (float)(i - 9) / (float)num; float num3 = (pointsUp ? (1f - num2) : num2); int num4 = Mathf.Max(1, Mathf.RoundToInt(9f * num3)); for (int j = 16 - num4; j <= 16 + num4; j++) { BlendPixel(pixels, j, i, color, 1f); } } } private static void DrawLine(Color32[] pixels, Vector2 start, Vector2 end, float width, Color32 color) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) float radius = width * 0.5f; float minimumX = Mathf.Min(start.x, end.x) - radius - 1f; float maximumX = Mathf.Max(start.x, end.x) + radius + 1f; float minimumY = Mathf.Min(start.y, end.y) - radius - 1f; float maximumY = Mathf.Max(start.y, end.y) + radius + 1f; Vector2 segment = end - start; float segmentLengthSquared = ((Vector2)(ref segment)).sqrMagnitude; ForEachPixel(minimumX, maximumX, minimumY, maximumY, delegate(int x, int y) { //IL_002b: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor((float)x + 0.5f, (float)y + 0.5f); float num = ((segmentLengthSquared > 0.0001f) ? Mathf.Clamp01(Vector2.Dot(val - start, segment) / segmentLengthSquared) : 0f); float num2 = Vector2.Distance(val, start + segment * num); BlendPixel(pixels, x, y, color, Mathf.Clamp01(radius + 0.5f - num2)); }); } private static void ForEachPixel(float minimumX, float maximumX, float minimumY, float maximumY, Action action) { int num = Mathf.Clamp(Mathf.FloorToInt(minimumX), 0, 31); int num2 = Mathf.Clamp(Mathf.CeilToInt(maximumX), 0, 31); int num3 = Mathf.Clamp(Mathf.FloorToInt(minimumY), 0, 31); int num4 = Mathf.Clamp(Mathf.CeilToInt(maximumY), 0, 31); for (int i = num3; i <= num4; i++) { for (int j = num; j <= num2; j++) { action(j, i); } } } private static void BlendPixel(Color32[] pixels, int x, int y, Color32 source, float coverage) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) if (!(coverage <= 0f)) { int num = y * 32 + x; Color32 val = pixels[num]; float num2 = (float)(int)source.a / 255f * coverage; float num3 = (float)(int)val.a / 255f; float num4 = num2 + num3 * (1f - num2); if (!(num4 <= 0f)) { float num5 = num2 / num4; float num6 = num3 * (1f - num2) / num4; pixels[num] = new Color32((byte)Mathf.Clamp(Mathf.RoundToInt((float)(int)source.r * num5 + (float)(int)val.r * num6), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt((float)(int)source.g * num5 + (float)(int)val.g * num6), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt((float)(int)source.b * num5 + (float)(int)val.b * num6), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt(num4 * 255f), 0, 255)); } } } private static void DestroyObject(Object? value) { if (!(value == (Object)null)) { if (Application.isPlaying) { Object.Destroy(value); } else { Object.DestroyImmediate(value); } } } } internal readonly struct ClanRecentPlayerEntry { internal ClanPlayerRef Player { get; } internal bool IsOnline { get; } internal long LastSeenUtcTicks { get; } internal ClanRecentPlayerEntry(ClanPlayerRef player, bool isOnline, long lastSeenUtcTicks) { Player = player; IsOnline = isOnline; LastSeenUtcTicks = lastSeenUtcTicks; } } internal static class ClanRecentPlayers { private sealed class RecentPlayersYaml { [YamlMember(Alias = "format_version", Order = 1)] public int FormatVersion { get; set; } [YamlMember(Alias = "players", Order = 2)] public List? Players { get; set; } } private sealed class RecentPlayerYaml { [YamlMember(Alias = "platform_id", Order = 1)] public string? PlatformId { get; set; } [YamlMember(Alias = "player_id", Order = 2)] public long PlayerId { get; set; } [YamlMember(Alias = "name", Order = 3)] public string? Name { get; set; } [YamlMember(Alias = "last_seen_utc", Order = 4)] public string? LastSeenUtc { get; set; } } private sealed class StoredRecentPlayer { internal ClanPlayerRef Player; internal long LastSeenUtcTicks; internal bool IsOnline; } private const int FormatVersion = 1; private const int MaximumPlayers = 4096; private const int MaximumSaveBytes = 4194304; private static readonly TimeSpan RecentLifetime = TimeSpan.FromDays(28.0); private static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(5.0); private static readonly TimeSpan SaveDebounce = TimeSpan.FromSeconds(2.0); private static readonly TimeSpan SaveRetryDelay = TimeSpan.FromSeconds(10.0); private static readonly TimeSpan PruneInterval = TimeSpan.FromMinutes(5.0); private static readonly Dictionary PlayersById = new Dictionary(StringComparer.Ordinal); private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly ISerializer YamlSerializer = new SerializerBuilder().DisableAliases().WithIndentedSequences().Build(); private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder().EnablePrivateConstructors().WithDuplicateKeyChecking().Build(); private static bool _initialized; private static ZNet? _loadedSession; private static long _loadedWorldUid; private static string? _loadedSaveFile; private static bool _dirty; private static bool _saveFailed; private static bool _primaryRecoveryRequired; private static bool _capacityWarningLogged; private static DateTime _nextPollUtc = DateTime.MinValue; private static DateTime _nextPruneUtc = DateTime.MinValue; private static DateTime _nextLoadAttemptUtc = DateTime.MinValue; private static DateTime _saveAfterUtc = DateTime.MaxValue; internal static void Init() { if (_initialized) { Dispose(); } _initialized = true; ResetMemory(); } internal static void Dispose() { if (_initialized) { CloseLoadedSession(DateTime.UtcNow, bypassSaveRetryDelay: true); _initialized = false; ResetMemory(); } } internal static void Tick() { if (!_initialized) { Init(); } DateTime utcNow = DateTime.UtcNow; if (EnsureServerSession(utcNow)) { if (utcNow >= _nextPollUtc) { _nextPollUtc = utcNow.Add(PollInterval); PollOnlinePlayers(utcNow); } if (utcNow >= _nextPruneUtc) { _nextPruneUtc = utcNow.Add(PruneInterval); RefreshOnlineLastSeen(utcNow); PruneExpiredPlayers(utcNow); } if (_dirty && utcNow >= _saveAfterUtc) { TrySave(utcNow, force: false); } } } internal static IReadOnlyList GetRecentPlayers() { DateTime utcNow = DateTime.UtcNow; if (!_initialized || !EnsureServerSession(utcNow)) { return Array.Empty(); } List list = new List(PlayersById.Count); foreach (StoredRecentPlayer value in PlayersById.Values) { if (value.IsOnline || IsRecent(value, utcNow)) { list.Add(new ClanRecentPlayerEntry(value.Player, value.IsOnline, Math.Min(value.LastSeenUtcTicks, utcNow.Ticks))); } } list.Sort(CompareEntries); return list; } internal static bool TryGetPlayer(string? playerId, out ClanPlayerRef player) { player = default(ClanPlayerRef); string text = ClanDataRules.NormalizePlayerKey(playerId); if (text.Length == 0) { return false; } DateTime utcNow = DateTime.UtcNow; if (!_initialized || !EnsureServerSession(utcNow) || !PlayersById.TryGetValue(text, out StoredRecentPlayer value) || (!value.IsOnline && !IsRecent(value, utcNow))) { return false; } player = value.Player; return player.IsValid; } internal static void RememberPlayer(ClanPlayerRef player) { if (player.IsValid) { DateTime utcNow = DateTime.UtcNow; if (_initialized && EnsureServerSession(utcNow)) { TryUpsertOnlinePlayer(player, utcNow); } } } internal static void MarkPlayerOffline(ClanPlayerRef player) { if (player.IsValid) { DateTime utcNow = DateTime.UtcNow; if (_initialized && EnsureServerSession(utcNow) && PlayersById.TryGetValue(player.Id, out StoredRecentPlayer value) && value.IsOnline) { value.IsOnline = false; value.LastSeenUtcTicks = utcNow.Ticks; MarkDirty(utcNow); } } } private static bool EnsureServerSession(DateTime nowUtc) { ZNet instance = ZNet.instance; World world = ZNet.World; if ((Object)(object)instance == (Object)null || !instance.IsServer() || world == null) { if (_loadedSaveFile != null) { CloseLoadedSession(nowUtc); } return false; } long uid = world.m_uid; if (_loadedSession == instance && _loadedWorldUid == uid && _loadedSaveFile != null) { return true; } if (nowUtc < _nextLoadAttemptUtc) { return false; } if (!CloseLoadedSession(nowUtc)) { return false; } _loadedSession = instance; _loadedWorldUid = uid; _loadedSaveFile = ResolveSaveFile(); try { LoadGlobalSave(nowUtc); } catch (Exception ex) when (IsSaveAccessError(ex)) { string loadedSaveFile = _loadedSaveFile; ResetMemory(); _nextLoadAttemptUtc = nowUtc.Add(SaveRetryDelay); ClanPlugin.ClanLogger.LogWarning((object)("Could not read recent-player data from '" + loadedSaveFile + "'. The file was left unchanged and loading will be retried: " + ex.Message)); return false; } _nextLoadAttemptUtc = DateTime.MinValue; _nextPollUtc = DateTime.MinValue; _nextPruneUtc = nowUtc.Add(PruneInterval); return true; } private static bool CloseLoadedSession(DateTime nowUtc, bool bypassSaveRetryDelay = false) { if (_loadedSaveFile != null) { MarkAllOffline(nowUtc); if (!TrySave(nowUtc, force: true, bypassSaveRetryDelay)) { return false; } } ResetMemory(); return true; } private static void PollOnlinePlayers(DateTime nowUtc) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (ClanPlayerRef onlinePlayerRef in ClanIdentity.GetOnlinePlayerRefs()) { if (onlinePlayerRef.IsValid) { dictionary[onlinePlayerRef.Id] = onlinePlayerRef; } } foreach (ClanPlayerRef value in dictionary.Values) { if (!TryUpsertOnlinePlayer(value, nowUtc) && !_capacityWarningLogged) { _capacityWarningLogged = true; ClanPlugin.ClanLogger.LogWarning((object)($"Recent-player storage reached its {4096}-player limit; " + "additional online players cannot be recorded until an offline entry expires.")); } } foreach (StoredRecentPlayer value2 in PlayersById.Values) { if (value2.IsOnline && !dictionary.ContainsKey(value2.Player.Id)) { value2.IsOnline = false; value2.LastSeenUtcTicks = nowUtc.Ticks; MarkDirty(nowUtc); } } if (PlayersById.Count < 4096) { _capacityWarningLogged = false; } } private static bool TryUpsertOnlinePlayer(ClanPlayerRef player, DateTime nowUtc) { if (!PlayersById.TryGetValue(player.Id, out StoredRecentPlayer value)) { if (!MakeRoomForPlayer(nowUtc)) { return false; } PlayersById.Add(player.Id, new StoredRecentPlayer { Player = player, LastSeenUtcTicks = nowUtc.Ticks, IsOnline = true }); MarkDirty(nowUtc); return true; } bool flag = false; if (!StringComparer.Ordinal.Equals(value.Player.Name, player.Name)) { value.Player = player; flag = true; } if (!value.IsOnline) { value.IsOnline = true; value.LastSeenUtcTicks = nowUtc.Ticks; flag = true; } if (flag) { MarkDirty(nowUtc); } return true; } private static void RefreshOnlineLastSeen(DateTime nowUtc) { bool flag = false; foreach (StoredRecentPlayer value in PlayersById.Values) { if (value.IsOnline && value.LastSeenUtcTicks != nowUtc.Ticks) { value.LastSeenUtcTicks = nowUtc.Ticks; flag = true; } } if (flag) { MarkDirty(nowUtc); } } private static void MarkAllOffline(DateTime nowUtc) { bool flag = false; foreach (StoredRecentPlayer value in PlayersById.Values) { if (value.IsOnline) { value.IsOnline = false; value.LastSeenUtcTicks = nowUtc.Ticks; flag = true; } } if (flag) { MarkDirty(nowUtc); } } private static bool MakeRoomForPlayer(DateTime nowUtc) { PruneExpiredPlayers(nowUtc); if (PlayersById.Count < 4096) { return true; } StoredRecentPlayer storedRecentPlayer = null; foreach (StoredRecentPlayer value in PlayersById.Values) { if (!value.IsOnline && (storedRecentPlayer == null || CompareOldest(value, storedRecentPlayer) < 0)) { storedRecentPlayer = value; } } if (storedRecentPlayer == null) { return false; } PlayersById.Remove(storedRecentPlayer.Player.Id); MarkDirty(nowUtc); return true; } private static int CompareOldest(StoredRecentPlayer left, StoredRecentPlayer right) { int num = left.LastSeenUtcTicks.CompareTo(right.LastSeenUtcTicks); if (num == 0) { return StringComparer.Ordinal.Compare(left.Player.Id, right.Player.Id); } return num; } private static void PruneExpiredPlayers(DateTime nowUtc) { List list = null; bool flag = false; foreach (StoredRecentPlayer value in PlayersById.Values) { if (value.LastSeenUtcTicks > nowUtc.Ticks) { value.LastSeenUtcTicks = nowUtc.Ticks; flag = true; } if (!value.IsOnline && !IsRecent(value, nowUtc)) { if (list == null) { list = new List(); } list.Add(value.Player.Id); } } if (list == null) { if (flag) { MarkDirty(nowUtc); } return; } foreach (string item in list) { PlayersById.Remove(item); } MarkDirty(nowUtc); } private static bool IsRecent(StoredRecentPlayer player, DateTime nowUtc) { long num = Math.Min(player.LastSeenUtcTicks, nowUtc.Ticks); DateTime minValue = DateTime.MinValue; if (num > minValue.Ticks) { long num2 = nowUtc.Ticks - num; TimeSpan recentLifetime = RecentLifetime; return num2 <= recentLifetime.Ticks; } return false; } private static int CompareEntries(ClanRecentPlayerEntry left, ClanRecentPlayerEntry right) { int num = right.IsOnline.CompareTo(left.IsOnline); if (num != 0) { return num; } int num2 = right.LastSeenUtcTicks.CompareTo(left.LastSeenUtcTicks); if (num2 != 0) { return num2; } int num3 = StringComparer.OrdinalIgnoreCase.Compare(left.Player.Name, right.Player.Name); if (num3 == 0) { return StringComparer.Ordinal.Compare(left.Player.Id, right.Player.Id); } return num3; } private static void LoadGlobalSave(DateTime nowUtc) { PlayersById.Clear(); _dirty = false; _saveFailed = false; _primaryRecoveryRequired = false; _saveAfterUtc = DateTime.MaxValue; string loadedSaveFile = _loadedSaveFile; string text = loadedSaveFile + ".bak"; if (TryLoadSave(loadedSaveFile, nowUtc, out Dictionary players, out bool needsRewrite, out Exception error)) { ReplacePlayers(players); PruneExpiredPlayers(nowUtc); if (needsRewrite) { MarkDirty(nowUtc); } return; } if (error != null) { string text2 = TryQuarantine(loadedSaveFile); _primaryRecoveryRequired = text2.Length == 0 && File.Exists(loadedSaveFile); ClanPlugin.ClanLogger.LogWarning((object)("Recent-player save '" + loadedSaveFile + "' was invalid and " + ((text2.Length == 0) ? "could not be quarantined" : ("was moved to '" + text2 + "'")) + ": " + error.Message)); } if (TryLoadSave(text, nowUtc, out players, out bool _, out Exception error2)) { ReplacePlayers(players); if (_primaryRecoveryRequired) { ClanPlugin.ClanLogger.LogWarning((object)("Loaded recent-player data from the validated backup '" + text + "', but the invalid primary could not be quarantined. Recovery will be retried without overwriting the valid backup.")); ScheduleSaveRetry(nowUtc); } else { try { WriteAtomically(loadedSaveFile, SerializePlayers()); ClanPlugin.ClanLogger.LogWarning((object)("Restored recent-player data from the validated backup '" + text + "'.")); } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Loaded recent-player backup '" + text + "', but failed to restore the primary save: " + ex.Message)); ScheduleSaveRetry(nowUtc); } } PruneExpiredPlayers(nowUtc); return; } if (error2 != null) { string text3 = TryQuarantine(text); ClanPlugin.ClanLogger.LogWarning((object)("Recent-player backup '" + text + "' was invalid and " + ((text3.Length == 0) ? "could not be quarantined" : ("was moved to '" + text3 + "'")) + ": " + error2.Message)); } PlayersById.Clear(); _dirty = false; _saveAfterUtc = DateTime.MaxValue; } private static bool TryLoadSave(string path, DateTime nowUtc, out Dictionary? players, out bool needsRewrite, out Exception? error) { players = null; needsRewrite = false; error = null; try { FileInfo fileInfo = new FileInfo(path); if (fileInfo.Length < 0 || fileInfo.Length > 4194304) { throw new InvalidDataException($"Recent-player save exceeds the {4194304}-byte limit."); } byte[] array = File.ReadAllBytes(path); if (array.Length > 4194304) { throw new InvalidDataException($"Recent-player save exceeds the {4194304}-byte limit."); } players = ParseSave(array, nowUtc, out needsRewrite); return true; } catch (FileNotFoundException) { return false; } catch (DirectoryNotFoundException) { return false; } catch (Exception ex3) when (IsInvalidSaveContent(ex3)) { error = ex3; return false; } } private static bool IsInvalidSaveContent(Exception error) { if (error is InvalidDataException || error is DecoderFallbackException || error is YamlException) { return true; } return false; } private static bool IsSaveAccessError(Exception error) { if (error is IOException || error is UnauthorizedAccessException || error is SecurityException) { return true; } return false; } private static Dictionary ParseSave(byte[] bytes, DateTime nowUtc, out bool needsRewrite) { needsRewrite = false; string input = StrictUtf8.GetString(bytes); RecentPlayersYaml recentPlayersYaml = YamlDeserializer.Deserialize(input); if (recentPlayersYaml == null) { throw new InvalidDataException("Recent-player save is empty."); } if (recentPlayersYaml.FormatVersion != 1) { throw new InvalidDataException($"Recent-player format version {recentPlayersYaml.FormatVersion} is unsupported; " + $"only version {1} is accepted."); } List list = recentPlayersYaml.Players ?? throw new InvalidDataException("Recent-player save is missing the players list."); if (list.Count > 4096) { throw new InvalidDataException($"Recent-player count exceeds the {4096}-player limit."); } Dictionary dictionary = new Dictionary(list.Count, StringComparer.Ordinal); bool flag = false; int num = 0; while (num < list.Count) { RecentPlayerYaml obj = list[num] ?? throw new InvalidDataException($"Recent-player entry {num} cannot be null."); string platformId = ClanDataRules.RequirePlatformId(obj.PlatformId, $"recent player {num} platform_id"); long playerId = ClanDataRules.RequireCharacterPlayerId(obj.PlayerId, $"recent player {num} player_id"); if (obj.Name == null) { throw new InvalidDataException($"Recent-player entry {num} is missing name."); } string name = ClanDataRules.RequireText(obj.Name, 64, $"recent player {num} name"); ClanPlayerRef player = new ClanPlayerRef(platformId, playerId, name); string text = ClanDataRules.RequireText(obj.LastSeenUtc, 64, "recent player '" + player.Id + "' last_seen_utc", allowEmpty: false); if (DateTime.TryParseExact(text, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result) && result.Kind == DateTimeKind.Utc && StringComparer.Ordinal.Equals(text, result.ToString("O", CultureInfo.InvariantCulture))) { long ticks = result.Ticks; DateTime minValue = DateTime.MinValue; if (ticks > minValue.Ticks) { if (dictionary.ContainsKey(player.Id)) { throw new InvalidDataException("Recent-player save contains duplicate identity '" + player.Id + "'."); } if (result.Ticks > nowUtc.Ticks) { result = nowUtc; flag = true; needsRewrite = true; } dictionary.Add(player.Id, new StoredRecentPlayer { Player = player, LastSeenUtcTicks = result.Ticks, IsOnline = false }); num++; continue; } } throw new InvalidDataException("Recent player '" + player.Id + "' has an invalid last-seen timestamp."); } if (flag) { ClanPlugin.ClanLogger.LogWarning((object)"Recent-player save contained future last-seen timestamps; they were clamped to the current UTC time."); } return dictionary; } private static void ReplacePlayers(Dictionary loaded) { PlayersById.Clear(); foreach (KeyValuePair item in loaded) { PlayersById.Add(item.Key, item.Value); } _dirty = false; _saveAfterUtc = DateTime.MaxValue; } private static bool TrySave(DateTime nowUtc, bool force, bool bypassRetryDelay = false) { if (!_dirty || _loadedSaveFile == null) { return true; } if (nowUtc < _saveAfterUtc && (!force || (_saveFailed && !bypassRetryDelay))) { return false; } try { if (_primaryRecoveryRequired && File.Exists(_loadedSaveFile)) { if (TryQuarantine(_loadedSaveFile).Length == 0 && File.Exists(_loadedSaveFile)) { throw new IOException("The invalid recent-player primary save could not be quarantined."); } _primaryRecoveryRequired = false; } WriteAtomically(_loadedSaveFile, SerializePlayers()); _dirty = false; _saveFailed = false; _saveAfterUtc = DateTime.MaxValue; return true; } catch (Exception ex) { _saveFailed = true; _saveAfterUtc = nowUtc.Add(SaveRetryDelay); ClanPlugin.ClanLogger.LogWarning((object)("Failed to save global recent-player data: " + ex.Message)); return false; } } private static byte[] SerializePlayers() { if (PlayersById.Count > 4096) { throw new InvalidDataException($"Recent-player count exceeds the {4096}-player limit."); } List list = new List(PlayersById.Values); list.Sort((StoredRecentPlayer left, StoredRecentPlayer right) => StringComparer.Ordinal.Compare(left.Player.Id, right.Player.Id)); List list2 = new List(list.Count); RecentPlayersYaml graph = new RecentPlayersYaml { FormatVersion = 1, Players = list2 }; foreach (StoredRecentPlayer item in list) { string platformId = ClanDataRules.RequirePlatformId(item.Player.PlatformId); long playerId = ClanDataRules.RequireCharacterPlayerId(item.Player.CharacterPlayerId); string name = ClanDataRules.RequireText(item.Player.Name, 64, "player name"); long lastSeenUtcTicks = item.LastSeenUtcTicks; DateTime minValue = DateTime.MinValue; if (lastSeenUtcTicks > minValue.Ticks) { long lastSeenUtcTicks2 = item.LastSeenUtcTicks; minValue = DateTime.MaxValue; if (lastSeenUtcTicks2 <= minValue.Ticks) { list2.Add(new RecentPlayerYaml { PlatformId = platformId, PlayerId = playerId, Name = name, LastSeenUtc = new DateTime(item.LastSeenUtcTicks, DateTimeKind.Utc).ToString("O", CultureInfo.InvariantCulture) }); continue; } } throw new InvalidDataException("Recent player '" + item.Player.Id + "' has an invalid last-seen timestamp."); } string text = YamlSerializer.Serialize(graph).Replace("\r\n", "\n").Replace('\r', '\n'); if (!text.EndsWith("\n", StringComparison.Ordinal)) { text += "\n"; } byte[] bytes = StrictUtf8.GetBytes(text); if (bytes.Length > 4194304) { throw new InvalidDataException($"Recent-player save exceeds the {4194304}-byte limit."); } return bytes; } private static void WriteAtomically(string saveFile, byte[] bytes) { string? directoryName = Path.GetDirectoryName(saveFile); if (string.IsNullOrWhiteSpace(directoryName)) { throw new InvalidOperationException("Recent-player save directory is invalid."); } Directory.CreateDirectory(directoryName); string text = saveFile + ".tmp-" + Guid.NewGuid().ToString("N"); try { using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(saveFile)) { string destinationBackupFileName = saveFile + ".bak"; File.Replace(text, saveFile, destinationBackupFileName, ignoreMetadataErrors: true); } else { File.Move(text, saveFile); } } finally { try { if (File.Exists(text)) { File.Delete(text); } } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Failed to clean temporary recent-player save '" + text + "': " + ex.Message)); } } } private static string TryQuarantine(string path) { string text = DateTime.UtcNow.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture); for (int i = 0; i < 10; i++) { string text2 = path + ".unsupported-or-corrupt-" + text + "-" + Guid.NewGuid().ToString("N"); try { File.Move(path, text2); return text2; } catch (FileNotFoundException) { return ""; } catch (DirectoryNotFoundException) { return ""; } catch (IOException) when (File.Exists(path) && File.Exists(text2)) { } } ClanPlugin.ClanLogger.LogWarning((object)("Could not allocate a quarantine path for recent-player save '" + path + "'.")); return ""; } private static string ResolveSaveFile() { return Path.Combine(ClanPlugin.DataDirectory, "recent-players.yml"); } private static void MarkDirty(DateTime nowUtc) { _dirty = true; if (!_saveFailed) { DateTime dateTime = nowUtc.Add(SaveDebounce); if (_saveAfterUtc == DateTime.MaxValue || dateTime < _saveAfterUtc) { _saveAfterUtc = dateTime; } } } private static void ScheduleSaveRetry(DateTime nowUtc) { _dirty = true; _saveFailed = true; _saveAfterUtc = nowUtc.Add(SaveRetryDelay); } private static void ResetMemory() { PlayersById.Clear(); _loadedSession = null; _loadedWorldUid = 0L; _loadedSaveFile = null; _dirty = false; _saveFailed = false; _primaryRecoveryRequired = false; _capacityWarningLogged = false; _nextPollUtc = DateTime.MinValue; _nextPruneUtc = DateTime.MinValue; _nextLoadAttemptUtc = DateTime.MinValue; _saveAfterUtc = DateTime.MaxValue; } } internal static class ClanPanelController { private enum PanelTab { Members, Players } private enum ConfirmAction { None, RejectApplication, Kick, TransferLeadership, Leave } private const float PanelWidth = 790f; private const float PanelHeight = 520f; private const float PreferredPanelScale = 2f; private const float SafeAreaGap = 8f; private const float MutationDirectoryRefreshDelay = 2.1f; private const float LeftPaneWidth = 358f; private const float RightPaneWidth = 408f; private const float RightContentWidth = 412f; private const float PanelScrollbarWidth = 2f; private const float ScrollOverflowEpsilon = 0.5f; private static readonly Color PanelColor = new Color(0.32f, 0.2f, 0.11f, 0.98f); private static readonly Color SectionColor = new Color(0f, 0f, 0f, 0.08f); private static readonly Color RowColor = new Color(0f, 0f, 0f, 0.18f); private static readonly Color AlternateRowColor = new Color(0f, 0f, 0f, 0.1f); private static readonly Color ButtonColor = new Color(0.78f, 0.78f, 0.78f, 0.96f); private static readonly Color InactiveToggleButtonColor = new Color(0.5f, 0.5f, 0.5f, 0.95f); private static readonly Color ActiveButtonColor = new Color(1f, 1f, 1f, 0.95f); private static readonly Color DisabledButtonColor = new Color(0.25f, 0.25f, 0.25f, 0.72f); private static readonly Color DangerColor = new Color(0.9f, 0.42f, 0.36f, 0.96f); private static readonly Color ActiveGuestBadgeColor = new Color(0.55f, 0.31f, 0.08f, 0.98f); private static readonly Color PickerButtonColor = new Color(0.18f, 0.18f, 0.18f, 0.26f); private static readonly Color ActivePickerButtonColor = new Color(0.55f, 0.31f, 0.08f, 0.72f); private static readonly Color DisabledPickerButtonColor = new Color(0f, 0f, 0f, 0.08f); private static readonly Color InactiveButtonLabelColor = Color.gray; private static readonly Color DisabledButtonLabelColor = new Color(0.4f, 0.4f, 0.4f, 0.9f); private static readonly Color MutedColor = new Color(0.853f, 0.725f, 0.533f, 1f); private static readonly Color AccentColor = new Color(1f, 0.631f, 0.235f, 1f); private static GameObject? _root; private static RectTransform? _rootRect; private static RectTransform? _overlayRoot; private static Button? _profileActionButton; private static Button? _membersTabButton; private static Text? _membersTabButtonLabel; private static Button? _playersTabButton; private static InputField? _clanSearchInput; private static InputField? _playerSearchInput; private static ScrollRect? _clanScroll; private static RectTransform? _clanScrollContent; private static ScrollRect? _peopleScroll; private static RectTransform? _peopleScrollContent; private static GameObject? _editorErrorBanner; private static Text? _editorErrorLabel; private static ClanClientSnapshot _snapshot = new ClanClientSnapshot(); private static ClanDirectorySnapshot _directory = new ClanDirectorySnapshot(); private static PanelTab _tab = PanelTab.Members; private static ConfirmAction _confirmAction; private static string _confirmTarget = ""; private static float _clanScrollPosition = 1f; private static float _rightScrollPosition = 1f; private static float _emblemScrollPosition = 1f; private static string _clanSearchQuery = ""; private static string _playerSearchQuery = ""; private static bool _resetClanScrollOnNextPopulate; private static bool _clanRowsDirty; private static bool _peopleRowsDirty; private static bool _positionValid; private static Rect _positionSafeArea; private static Rect _positionOverlayBounds; private static bool _editorOpen; private static bool _editorCreating; private static bool _editorSubmissionPending; private static long _editorSubmissionRequestId; private static string _draftName = ""; private static string _draftDescription = ""; private static string _draftEmblemKey = ""; private static string _editorError = ""; private static float _directoryRefreshAt = float.PositiveInfinity; private static bool _directoryPendingAtLastBuild; public static bool IsOpen { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public static bool CapturesGameplayInput { get { if ((Object)(object)_root != (Object)null) { return _root.activeInHierarchy; } return false; } } public static bool OwnsSelectedControl { get { EventSystem current = EventSystem.current; if (IsOpen && (Object)(object)current != (Object)null) { return IsOwnedControl(current.currentSelectedGameObject); } return false; } } public static bool OwnsFocusedTextInput { get { EventSystem current = EventSystem.current; if (!IsOpen || (Object)(object)current == (Object)null) { return false; } GameObject currentSelectedGameObject = current.currentSelectedGameObject; if ((Object)(object)currentSelectedGameObject == (Object)null || !IsOwnedControl(currentSelectedGameObject)) { return false; } InputField component = currentSelectedGameObject.GetComponent(); if ((Object)(object)component != (Object)null) { return component.isFocused; } return false; } } public static event Action? OpenStateChanged; private static bool IsOwnedControl(GameObject? selected) { if ((Object)(object)_root == (Object)null || (Object)(object)selected == (Object)null) { return false; } if (!((Object)(object)selected == (Object)(object)_root)) { return selected.transform.IsChildOf(_root.transform); } return true; } private static void ClearOwnedSelection() { EventSystem current = EventSystem.current; if ((Object)(object)current != (Object)null && IsOwnedControl(current.currentSelectedGameObject)) { current.SetSelectedGameObject((GameObject)null); } } private static void ClearSelectionWithin(Transform parent) { EventSystem current = EventSystem.current; GameObject val = ((current != null) ? current.currentSelectedGameObject : null); if ((Object)(object)current != (Object)null && (Object)(object)val != (Object)null && ((Object)(object)val.transform == (Object)(object)parent || val.transform.IsChildOf(parent))) { current.SetSelectedGameObject((GameObject)null); } } public static void Build(Transform parent, RectTransform overlayRoot, bool preserveInteractionState = false) { //IL_0059: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) DestroyView(preserveInteractionState); _overlayRoot = overlayRoot; _snapshot = ClanRpc.CurrentSnapshot; _directory = ClanRpc.CurrentDirectory; _root = CreateWoodPanelObject("ClanPanel", parent, 790f, 520f, draggable: true); _rootRect = _root.GetComponent(); _rootRect.anchorMin = new Vector2(0.5f, 0.5f); _rootRect.anchorMax = new Vector2(0.5f, 0.5f); _rootRect.pivot = new Vector2(0.5f, 0.5f); _rootRect.sizeDelta = new Vector2(790f, 520f); _rootRect.anchoredPosition = Vector2.zero; ((Graphic)_root.GetComponent()).raycastTarget = true; _root.SetActive(false); } public static void Tick() { RefreshMembersNotificationPulse(); if (IsOpen && !_editorOpen) { if (_clanRowsDirty) { _clanRowsDirty = false; PopulateClanRows(); } if (_peopleRowsDirty) { _peopleRowsDirty = false; PopulatePeopleRows(); } } if (IsOpen && !float.IsPositiveInfinity(_directoryRefreshAt) && !(Time.unscaledTime < _directoryRefreshAt) && !ClanRpc.IsDirectoryRequestPending) { _directoryRefreshAt = ((ClanRpc.RequestDirectory() > 0) ? float.PositiveInfinity : (Time.unscaledTime + 2.1f)); } } public static void DestroyView(bool preserveInteractionState = false) { ClanUiFeedback.HideTooltip(); ClearOwnedSelection(); GameObject root = _root; bool num = (Object)(object)root != (Object)null && root.activeSelf; _root = null; _rootRect = null; _overlayRoot = null; _profileActionButton = null; _membersTabButton = null; _membersTabButtonLabel = null; _playersTabButton = null; _clanSearchInput = null; _playerSearchInput = null; _clanScroll = null; _clanScrollContent = null; _peopleScroll = null; _peopleScrollContent = null; _editorErrorBanner = null; _editorErrorLabel = null; _positionValid = false; if (!preserveInteractionState) { _editorOpen = false; ClearEditorSubmission(); ClearEditorError(); _directoryRefreshAt = float.PositiveInfinity; ClearConfirmation(); } _directoryPendingAtLastBuild = false; _resetClanScrollOnNextPopulate = false; _clanRowsDirty = false; _peopleRowsDirty = false; if ((Object)(object)root != (Object)null) { root.SetActive(false); Object.Destroy((Object)(object)root); } if (num) { ClanPanelController.OpenStateChanged?.Invoke(obj: false); } } public static void Toggle() { if (!((Object)(object)_root == (Object)null)) { if (_root.activeSelf) { Close(); return; } _root.SetActive(true); _root.transform.SetAsLastSibling(); RebuildView(); PositionPanel(); ClanPanelController.OpenStateChanged?.Invoke(obj: true); } } public static void Close() { if (!((Object)(object)_root == (Object)null)) { bool activeSelf = _root.activeSelf; ClanUiFeedback.HideTooltip(); ClearOwnedSelection(); _editorOpen = false; ClearEditorSubmission(); ClearConfirmation(); _root.SetActive(false); if (activeSelf) { ClanPanelController.OpenStateChanged?.Invoke(obj: false); } } } public static bool HandleCancelInput() { if (!IsOpen) { return false; } ClanUiFeedback.HideTooltip(); ClearOwnedSelection(); if (_editorOpen) { CloseEditor(); return true; } if (_confirmAction != ConfirmAction.None) { ClearConfirmation(); RebuildView(); return true; } Close(); return true; } public static bool ContainsPointer(Vector2 pointerPosition) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rootRect != (Object)null && ((Component)_rootRect).gameObject.activeInHierarchy) { return RectTransformUtility.RectangleContainsScreenPoint(_rootRect, pointerPosition, ClanUiFactory.GetCanvasCamera((Component?)(object)_rootRect)); } return false; } public static void RefreshPosition(RectTransform overlayRoot) { _overlayRoot = overlayRoot; if (IsOpen) { PositionPanel(); } } public static void RefreshSnapshot(ClanClientSnapshot snapshot) { ClanClientSnapshot clanClientSnapshot = snapshot ?? new ClanClientSnapshot(); string x = _snapshot.Invite?.InviteId ?? ""; string y = clanClientSnapshot.Invite?.InviteId ?? ""; bool flag = clanClientSnapshot.Invite != null && !StringComparer.Ordinal.Equals(x, y); string clanId = _snapshot.ClanId; bool flag2 = !StringComparer.Ordinal.Equals(clanId, clanClientSnapshot.ClanId); bool flag3 = !StringComparer.Ordinal.Equals(_snapshot.PrimaryClanId, clanClientSnapshot.PrimaryClanId) || !StringComparer.Ordinal.Equals(_snapshot.GuestClanId, clanClientSnapshot.GuestClanId) || !StringComparer.Ordinal.Equals(x, y) || !StringComparer.Ordinal.Equals(_snapshot.OwnApplicationClanId, clanClientSnapshot.OwnApplicationClanId); bool num = _snapshot != clanClientSnapshot; bool flag4 = !HasSameSnapshotPresentation(_snapshot, clanClientSnapshot); bool flag5 = _snapshot.CanModerate != clanClientSnapshot.CanModerate; bool editorOpen = _editorOpen; bool flag6 = num && _editorSubmissionPending && _editorSubmissionRequestId > 0 && clanClientSnapshot.ResponseRequestId == _editorSubmissionRequestId; bool flag7 = flag6 && SubmittedProfileMatches(clanClientSnapshot); _snapshot = clanClientSnapshot; if (flag) { _clanScrollPosition = 1f; _resetClanScrollOnNextPopulate = true; } if (flag5 || flag2 || flag3) { ClanRpc.InvalidateDirectory(); _directory = ClanRpc.CurrentDirectory; ScheduleDirectoryRefresh(); } if (flag4) { ClearConfirmation(); } if (num) { if (_editorOpen && !_editorCreating && (!_snapshot.HasClan || !_snapshot.IsLeader)) { _editorOpen = false; ClearEditorSubmission(); ClearEditorError(); } else if (flag7) { _editorOpen = false; ClearEditorSubmission(); ClearEditorError(); } else if (flag6) { ClearEditorSubmission(); ShowEditorError(string.IsNullOrWhiteSpace(_snapshot.Status) ? ClanLocalization.Text("panel_profile_save_failed") : ClanLocalization.ResolveStatus(_snapshot.Status)); if (IsOpen) { RebuildView(); } } } bool flag8 = editorOpen && !_editorOpen; if (IsOpen && flag8 && !_editorOpen) { RebuildView(); } else if (IsOpen && flag4 && !_editorOpen) { RefreshHeaderState(); _clanRowsDirty = true; _peopleRowsDirty = true; } } public static void RefreshDirectory(ClanDirectorySnapshot directory) { ClanDirectorySnapshot clanDirectorySnapshot = directory ?? new ClanDirectorySnapshot(); if (clanDirectorySnapshot.RequestId > 0) { _directoryRefreshAt = float.PositiveInfinity; } bool flag = !HasSameDirectoryPresentation(_directory, clanDirectorySnapshot); _directory = clanDirectorySnapshot; bool flag2 = IsOpen && _directoryPendingAtLastBuild != ClanRpc.IsDirectoryRequestPending; if (IsOpen && (flag || flag2) && !_editorOpen) { _clanRowsDirty = true; if (_tab == PanelTab.Players) { _peopleRowsDirty = true; } } } private static bool HasSameSnapshotPresentation(ClanClientSnapshot left, ClanClientSnapshot right) { if (StringComparer.Ordinal.Equals(left.ClanId, right.ClanId) && StringComparer.Ordinal.Equals(left.PrimaryClanId, right.PrimaryClanId) && StringComparer.Ordinal.Equals(left.PrimaryClanName, right.PrimaryClanName) && left.PrimaryRole == right.PrimaryRole && StringComparer.Ordinal.Equals(left.GuestClanId, right.GuestClanId) && StringComparer.Ordinal.Equals(left.GuestClanName, right.GuestClanName) && StringComparer.Ordinal.Equals(left.ClanName, right.ClanName) && StringComparer.Ordinal.Equals(left.ClanDescription, right.ClanDescription) && StringComparer.Ordinal.Equals(left.ClanEmblemKey, right.ClanEmblemKey) && left.SelfRole == right.SelfRole && HaveSameItems(left.Roster, right.Roster, HasSameRosterPlayer) && HaveSameItems(left.Applications, right.Applications, HasSameApplication) && HasSameInvite(left.Invite, right.Invite) && StringComparer.Ordinal.Equals(left.OwnApplicationClanId, right.OwnApplicationClanId)) { return StringComparer.Ordinal.Equals(left.OwnApplicationClanName, right.OwnApplicationClanName); } return false; } private static bool HasSameDirectoryPresentation(ClanDirectorySnapshot left, ClanDirectorySnapshot right) { if (left.IsTruncated == right.IsTruncated && HaveSameItems(left.PublicClans, right.PublicClans, HasSamePublicClan)) { return HaveSameItems(left.Players, right.Players, HasSameDirectoryPlayer); } return false; } private static bool HasSameRosterPlayer(ClanPlayerSummary left, ClanPlayerSummary right) { if (StringComparer.Ordinal.Equals(left.Id, right.Id) && StringComparer.Ordinal.Equals(left.Name, right.Name) && left.Role == right.Role && left.IsSelf == right.IsSelf) { return left.IsOnline == right.IsOnline; } return false; } private static bool HasSameApplication(ClanApplicationSummary left, ClanApplicationSummary right) { if (StringComparer.Ordinal.Equals(left.PlayerId, right.PlayerId)) { return StringComparer.Ordinal.Equals(left.PlayerName, right.PlayerName); } return false; } private static bool HasSameInvite(ClanInviteSummary? left, ClanInviteSummary? right) { if (left == right) { return true; } if (left != null && right != null && StringComparer.Ordinal.Equals(left.InviteId, right.InviteId) && StringComparer.Ordinal.Equals(left.ClanId, right.ClanId) && StringComparer.Ordinal.Equals(left.ClanName, right.ClanName)) { return StringComparer.Ordinal.Equals(left.FromName, right.FromName); } return false; } private static bool HasSamePublicClan(ClanPublicSummary left, ClanPublicSummary right) { if (StringComparer.Ordinal.Equals(left.ClanId, right.ClanId) && StringComparer.Ordinal.Equals(left.Name, right.Name) && StringComparer.Ordinal.Equals(left.Description, right.Description) && StringComparer.Ordinal.Equals(left.EmblemKey, right.EmblemKey)) { return StringComparer.Ordinal.Equals(left.LeaderName, right.LeaderName); } return false; } private static bool HasSameDirectoryPlayer(ClanDirectoryPlayerSummary left, ClanDirectoryPlayerSummary right) { if (StringComparer.Ordinal.Equals(left.PlayerId, right.PlayerId) && StringComparer.Ordinal.Equals(left.PlayerName, right.PlayerName) && left.State == right.State && StringComparer.Ordinal.Equals(left.ClanName, right.ClanName) && left.IsOnline == right.IsOnline && left.IsSelf == right.IsSelf && left.CanInvite == right.CanInvite && left.CanResolveApplication == right.CanResolveApplication) { return left.LastSeenUtcTicks == right.LastSeenUtcTicks; } return false; } private static bool HaveSameItems(IReadOnlyList left, IReadOnlyList right, Func equals) { if (left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { if (!equals(left[i], right[i])) { return false; } } return true; } public static void OnEmblemsChanged() { if (IsOpen) { if (_editorOpen) { RebuildView(); } else { _clanRowsDirty = true; } } } private static void RebuildView() { if (!((Object)(object)_root == (Object)null)) { ClanUiFeedback.HideTooltip(); ClearOwnedSelection(); ClanUiFactory.ClearChildren(_root.transform); _profileActionButton = null; _membersTabButton = null; _membersTabButtonLabel = null; _playersTabButton = null; _clanSearchInput = null; _playerSearchInput = null; _clanScroll = null; _clanScrollContent = null; _peopleScroll = null; _peopleScrollContent = null; _clanRowsDirty = false; _peopleRowsDirty = false; _editorErrorBanner = null; _editorErrorLabel = null; _directoryPendingAtLastBuild = ClanRpc.IsDirectoryRequestPending; BuildHeader(_root.transform); BuildOverview(_root.transform); BuildPeoplePane(_root.transform); if (_editorOpen) { BuildEditor(_root.transform); } } } private static void BuildHeader(Transform parent) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Unknown result type (might be due to invalid IL or missing references) GameObject val = CreateRect("Header", parent, 8f, 464f, 774f, 48f, typeof(Image)); ((Graphic)val.GetComponent()).color = SectionColor; CreateLabel(val.transform, ClanLocalization.Text("panel_title"), 10f, 7f, 48f, 34f, 19, (TextAnchor)3, ClanUiFactory.GetClanColor()).fontStyle = (FontStyle)1; bool flag = !_snapshot.HasAnyClan; bool isLeader = _snapshot.IsLeader; _profileActionButton = CreateIconButton(val.transform, ClanActionIcon.Edit, flag ? ClanLocalization.Text("panel_tooltip_create_profile") : (isLeader ? ClanLocalization.Text("panel_tooltip_edit_profile") : ((_snapshot.HasGuestClan && _snapshot.PrimaryRole == ClanRole.Leader) ? ClanLocalization.Text("panel_tooltip_leave_guest_to_edit") : ClanLocalization.Text("panel_tooltip_leader_only_edit"))), flag ? new Action(OpenCreateEditor) : new Action(OpenEditEditor), 90f, 7f, 40f, 34f); ((Object)_profileActionButton).name = (flag ? "CreateClanProfile" : "EditClanProfile"); if (!flag && !isLeader) { ((Selectable)_profileActionButton).interactable = false; SetButtonContentColor(_profileActionButton, DisabledButtonLabelColor); } _clanSearchInput = CreateInputField(val.transform, "ClanSearch", _clanSearchQuery, ClanLocalization.Text("panel_search_clans"), 136f, 7f, 140f, 34f, 64, 11); ((UnityEvent)(object)_clanSearchInput.onValueChanged).AddListener((UnityAction)OnClanSearchChanged); ZNet instance = ZNet.instance; if (instance != null && instance.IsServer()) { ((Object)CreateIconButton(val.transform, ClanActionIcon.Folder, ClanLocalization.Text("panel_tooltip_open_media_folder"), OpenClanMediaDirectory, 282f, 7f, 40f, 34f)).name = "OpenClanMediaDirectory"; } bool flag2 = _tab == PanelTab.Members; _membersTabButton = CreateButton(val.transform, ClanLocalization.Text("panel_tab_members"), delegate { SelectTab(PanelTab.Members); }, 355f, 7f, 94f, 34f, flag2 ? ActiveButtonColor : InactiveToggleButtonColor, 13); _membersTabButtonLabel = ((Component)_membersTabButton).GetComponentInChildren(true); RefreshMembersNotificationPulse(); bool flag3 = _tab == PanelTab.Players; _playersTabButton = CreateButton(val.transform, ClanLocalization.Text("panel_tab_players"), delegate { SelectTab(PanelTab.Players); }, 455f, 7f, 94f, 34f, flag3 ? ActiveButtonColor : InactiveToggleButtonColor, 13); SetButtonLabelColor(_playersTabButton, flag3 ? Color.white : InactiveButtonLabelColor); _playerSearchInput = CreateInputField(val.transform, "PlayerSearch", _playerSearchQuery, ClanLocalization.Text("panel_search_player_clan"), 555f, 7f, 140f, 34f, 64, 11); ((UnityEvent)(object)_playerSearchInput.onValueChanged).AddListener((UnityAction)OnPlayerSearchChanged); CreateButton(val.transform, "×", Close, 724f, 7f, 40f, 34f, ButtonColor, 20); } private static void OpenClanMediaDirectory() { if (GUIManager.IsHeadless()) { return; } ZNet instance = ZNet.instance; if (instance == null || !instance.IsServer()) { return; } string text = ""; try { text = Path.GetFullPath(ClanPlugin.MediaDirectory); Directory.CreateDirectory(text); ClanUiFeedback.HideTooltip(); string text2 = text; char directorySeparatorChar = Path.DirectorySeparatorChar; Application.OpenURL(text2 + directorySeparatorChar); } catch (Exception ex) { ClanPlugin.ClanLogger.LogWarning((object)("Could not open Clan media directory '" + text + "': " + ex.Message)); } } private static void RefreshHeaderState() { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) Button profileActionButton = _profileActionButton; if ((Object)(object)profileActionButton != (Object)null) { bool flag = !_snapshot.HasAnyClan; bool isLeader = _snapshot.IsLeader; string tooltip = (flag ? ClanLocalization.Text("panel_tooltip_create_profile") : (isLeader ? ClanLocalization.Text("panel_tooltip_edit_profile") : ((_snapshot.HasGuestClan && _snapshot.PrimaryRole == ClanRole.Leader) ? ClanLocalization.Text("panel_tooltip_leave_guest_to_edit") : ClanLocalization.Text("panel_tooltip_leader_only_edit")))); ((Object)profileActionButton).name = (flag ? "CreateClanProfile" : "EditClanProfile"); ((UnityEventBase)profileActionButton.onClick).RemoveAllListeners(); UnityAction val = (flag ? new UnityAction(OpenCreateEditor) : new UnityAction(OpenEditEditor)); ((UnityEvent)profileActionButton.onClick).AddListener(val); ((Selectable)profileActionButton).interactable = flag || isLeader; SetButtonContentColor(profileActionButton, ((Selectable)profileActionButton).interactable ? Color.white : DisabledButtonLabelColor); ClanUiFeedback.SetTooltip((Selectable)(object)profileActionButton, tooltip); } bool flag2 = _tab == PanelTab.Members; if ((Object)(object)_membersTabButton != (Object)null) { SetButtonColor(_membersTabButton, flag2 ? ActiveButtonColor : InactiveToggleButtonColor); RefreshMembersNotificationPulse(); } bool flag3 = _tab == PanelTab.Players; if ((Object)(object)_playersTabButton != (Object)null) { SetButtonColor(_playersTabButton, flag3 ? ActiveButtonColor : InactiveToggleButtonColor); SetButtonLabelColor(_playersTabButton, flag3 ? Color.white : InactiveButtonLabelColor); } } private static bool HasPendingApplications() { if (_snapshot.CanModerate) { return _snapshot.Applications.Count > 0; } return false; } private static void RefreshMembersNotificationPulse() { //IL_003c: 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_002e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_membersTabButton == (Object)null) && !((Object)(object)_membersTabButtonLabel == (Object)null)) { ((Graphic)_membersTabButtonLabel).color = (HasPendingApplications() ? ClanUiFeedback.GetNotificationPulseColor() : ((_tab == PanelTab.Members) ? Color.white : InactiveButtonLabelColor)); } } private static void BuildOverview(Transform parent) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) GameObject obj = CreateRect("ClanOverview", parent, 8f, 8f, 358f, 456f, typeof(Image)); ((Graphic)obj.GetComponent()).color = SectionColor; _clanScroll = CreateScrollView(obj.transform, "ClanRows", 8f, 4f, 342f, 448f, out _clanScrollContent, _clanScrollPosition, delegate(float value) { _clanScrollPosition = value; }, 4f); PopulateClanRows(); } private static void PopulateClanRows() { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) ScrollRect clanScroll = _clanScroll; RectTransform clanScrollContent = _clanScrollContent; if (!((Object)(object)clanScroll == (Object)null) && !((Object)(object)clanScrollContent == (Object)null)) { ClanUiFeedback.HideTooltip(); ClearSelectionWithin((Transform)(object)clanScrollContent); clanScroll.StopMovement(); ClanUiFactory.ClearChildren((Transform)(object)clanScrollContent); _clanRowsDirty = false; _directoryPendingAtLastBuild = ClanRpc.IsDirectoryRequestPending; IReadOnlyList publicClans = _directory.PublicClans; IReadOnlyList readOnlyList = publicClans.Where(MatchesClanSearch).ToArray(); if (readOnlyList.Count == 0) { string text = ((publicClans.Count > 0 && !string.IsNullOrEmpty(_clanSearchQuery)) ? ClanLocalization.Text("panel_no_clans_match") : ((ClanRpc.IsDirectoryRequestPending || !float.IsPositiveInfinity(_directoryRefreshAt)) ? ClanLocalization.Text("panel_loading_clans") : ClanLocalization.Text("panel_no_clans"))); CreateTopLabel((Transform)(object)clanScrollContent, text, 8f, 202f, 326f, 44f, 13, (TextAnchor)4, MutedColor); } for (int i = 0; i < readOnlyList.Count; i++) { BuildClanRow(clanScrollContent, readOnlyList[i], 4f + (float)i * 80f, 76f, i % 2 == 1); } float requestedHeight = 4f + (float)readOnlyList.Count * 80f; SetScrollContentHeight(clanScroll, clanScrollContent, requestedHeight, 448f); if (_resetClanScrollOnNextPopulate) { _clanScrollPosition = 1f; _resetClanScrollOnNextPopulate = false; } clanScroll.verticalNormalizedPosition = Mathf.Clamp01(_clanScrollPosition); } } private static void BuildClanRow(RectTransform content, ClanPublicSummary clan, float top, float height, bool alternate) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) GameObject val = ClanUiFactory.CreateObject("ClanRow." + clan.ClanId, (Transform)(object)content, typeof(Image)); PlaceTop(val.GetComponent(), 3f, top, 336f, height); bool flag = _snapshot.IsConnectedToClan(clan.ClanId); bool num = StringComparer.Ordinal.Equals(_snapshot.ClanId, clan.ClanId); bool flag2 = num && StringComparer.Ordinal.Equals(_snapshot.GuestClanId, clan.ClanId); ClanInviteSummary selectedInvite = ((!flag && _snapshot.Invite != null && StringComparer.Ordinal.Equals(_snapshot.Invite.ClanId, clan.ClanId)) ? _snapshot.Invite : null); if (num) { Color clanColor = ClanUiFactory.GetClanColor(); ((Graphic)val.GetComponent()).color = new Color(clanColor.r * 0.35f, clanColor.g * 0.35f, clanColor.b * 0.35f, 0.72f); } else { ((Graphic)val.GetComponent()).color = (Color)(flag ? new Color(0.18f, 0.14f, 0.1f, 0.72f) : (alternate ? AlternateRowColor : RowColor)); } AddEmblem(val.transform, clan.EmblemKey, 6f, 7f, 62f, 62f, clan.Name); Text obj = CreateLabel(val.transform, clan.Name, 76f, 49f, 100f, 22f, 12, (TextAnchor)3, ClanUiFactory.GetClanColor()); obj.resizeTextForBestFit = true; obj.resizeTextMinSize = 8; obj.resizeTextMaxSize = 12; Text obj2 = CreateLabel(val.transform, ClanLocalization.Format("panel_leader_name", clan.LeaderName), 180f, 49f, 76f, 22f, 9, (TextAnchor)5, MutedColor); obj2.resizeTextForBestFit = true; obj2.resizeTextMinSize = 7; obj2.resizeTextMaxSize = 9; CreateLabel(val.transform, string.IsNullOrWhiteSpace(clan.Description) ? "—" : clan.Description, 76f, 5f, flag2 ? 166f : 254f, 42f, 9, (TextAnchor)0, string.IsNullOrWhiteSpace(clan.Description) ? MutedColor : Color.white).verticalOverflow = (VerticalWrapMode)0; if (flag2) { GameObject obj3 = ClanUiFactory.CreateObject("ActiveGuestBadge", val.transform, typeof(Image)); Place(obj3.GetComponent(), 246f, 29f, 84f, 16f); Image component = obj3.GetComponent(); ((Graphic)component).color = ActiveGuestBadgeColor; ((Graphic)component).raycastTarget = false; Text obj4 = CreateLabel(obj3.transform, ClanLocalization.Text("panel_active_guest"), 3f, 0f, 78f, 16f, 8, (TextAnchor)4, Color.white); obj4.resizeTextForBestFit = true; obj4.resizeTextMinSize = 6; obj4.resizeTextMaxSize = 8; } BuildClanRowAction(val.transform, clan, selectedInvite); } private static void BuildClanRowAction(Transform parent, ClanPublicSummary clan, ClanInviteSummary? selectedInvite) { //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) if (_snapshot.IsConnectedToClan(clan.ClanId)) { if (!StringComparer.Ordinal.Equals(_snapshot.ClanId, clan.ClanId)) { CreateDisabledClanAction(parent, ClanLocalization.Text("panel_action_inactive"), BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_inactive_guest", _snapshot.GuestClanName)), 260f, 48f, 70f, 23f); return; } bool flag = _snapshot.IsLeader && _snapshot.Roster.Any((ClanPlayerSummary member) => !member.IsSelf && member.Role != ClanRole.Guest); bool flag2 = _snapshot.IsLeader && !flag; bool flag3 = !flag && IsConfirming(ConfirmAction.Leave, clan.ClanId); if (flag) { CreateDisabledClanAction(parent, ClanLocalization.Text("panel_action_transfer"), BuildClanRowTooltip(clan, ClanLocalization.Text("panel_tooltip_transfer_before_leaving")), 260f, 48f, 70f, 23f); return; } ClanUiFeedback.SetTooltip((Selectable)(object)CreateButton(parent, flag3 ? ClanLocalization.Text("common_confirm_question") : (flag2 ? ClanLocalization.Text("panel_action_disband") : ClanLocalization.Text("panel_action_leave")), delegate { ConfirmThen(ConfirmAction.Leave, clan.ClanId, delegate { SendRequest(new ClanRequest { Type = ClanRequestType.LeaveClan, ClanId = clan.ClanId }); }); }, 260f, 48f, 70f, 23f, flag3 ? DangerColor : ButtonColor, 9), BuildClanRowTooltip(clan, flag3 ? ClanLocalization.Text("common_click_again_confirm") : (flag2 ? ClanLocalization.Format("panel_tooltip_disband", clan.Name) : ClanLocalization.Format("panel_tooltip_leave", clan.Name)))); } else if (selectedInvite != null) { ClanInviteSummary invite = selectedInvite; ((Object)CreateIconButton(parent, ClanActionIcon.Accept, BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_accept_invite", invite.ClanName, invite.FromName)), delegate { SendRequest(new ClanRequest { Type = ClanRequestType.AcceptInvite, InviteId = invite.InviteId }); }, 260f, 48f, 33f, 23f, ButtonColor)).name = "AcceptInvite"; ((Object)CreateIconButton(parent, ClanActionIcon.Decline, BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_decline_invite", invite.ClanName, invite.FromName)), delegate { SendRequest(new ClanRequest { Type = ClanRequestType.DeclineInvite, InviteId = invite.InviteId }); }, 297f, 48f, 33f, 23f, ButtonColor)).name = "DeclineInvite"; } else if (_snapshot.HasOwnApplication) { if (StringComparer.Ordinal.Equals(_snapshot.OwnApplicationClanId, clan.ClanId)) { ClanUiFeedback.SetTooltip((Selectable)(object)CreateButton(parent, ClanLocalization.Text("common_cancel"), delegate { SendRequest(ClanRequest.Simple(ClanRequestType.CancelApplication)); }, 260f, 48f, 70f, 23f, ButtonColor, 9), BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_cancel_application", clan.Name))); } else { CreateDisabledClanAction(parent, ClanLocalization.Text("panel_state_pending"), BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_application_pending", _snapshot.OwnApplicationClanName)), 260f, 48f, 70f, 23f); } } else if (_snapshot.HasGuestClan) { CreateDisabledClanAction(parent, ClanLocalization.Text("panel_state_unavailable"), BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_guest_slot_used", _snapshot.GuestClanName)), 260f, 48f, 70f, 23f); } else { ClanUiFeedback.SetTooltip((Selectable)(object)CreateButton(parent, ClanLocalization.Text("panel_action_apply"), delegate { ApplyToClan(clan.ClanId); }, 260f, 48f, 70f, 23f, ActiveButtonColor, 9), BuildClanRowTooltip(clan, ClanLocalization.Format("panel_tooltip_apply", clan.Name))); } } private static string BuildClanRowTooltip(ClanPublicSummary clan, string action) { string text = (string.IsNullOrWhiteSpace(clan.Description) ? ClanLocalization.Text("panel_no_description") : clan.Description); return ClanLocalization.Format("panel_clan_tooltip_summary", action, clan.Name, clan.LeaderName, text); } private static void CreateDisabledClanAction(Transform parent, string text, string tooltip, float x, float y, float width, float height) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Button obj = CreateButton(parent, text, delegate { }, x, y, width, height, ButtonColor, 8); ColorBlock colors = ((Selectable)obj).colors; ((ColorBlock)(ref colors)).disabledColor = DisabledButtonColor; ((Selectable)obj).colors = colors; ((Selectable)obj).interactable = false; SetButtonLabelColor(obj, DisabledButtonLabelColor); ClanUiFeedback.SetTooltip((Selectable)(object)obj, tooltip); } private static void BuildPeoplePane(Transform parent) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) GameObject obj = CreateRect("PeoplePane", parent, 366f, 8f, 408f, 456f, typeof(Image)); ((Graphic)obj.GetComponent()).color = SectionColor; _peopleScroll = CreateScrollView(obj.transform, "PeopleScroll", -3f, 4f, 411f, 448f, out _peopleScrollContent, _rightScrollPosition, delegate(float value) { _rightScrollPosition = value; }, 6f); PopulatePeopleRows(); } private static void PopulatePeopleRows() { ScrollRect peopleScroll = _peopleScroll; RectTransform peopleScrollContent = _peopleScrollContent; if (!((Object)(object)peopleScroll == (Object)null) && !((Object)(object)peopleScrollContent == (Object)null)) { ClanUiFeedback.HideTooltip(); ClearSelectionWithin((Transform)(object)peopleScrollContent); peopleScroll.StopMovement(); ClanUiFactory.ClearChildren((Transform)(object)peopleScrollContent); _peopleRowsDirty = false; _directoryPendingAtLastBuild = ClanRpc.IsDirectoryRequestPending; float requestedHeight = ((_tab == PanelTab.Members) ? BuildMembersTab(peopleScrollContent) : BuildPlayersTab(peopleScrollContent)); SetScrollContentHeight(peopleScroll, peopleScrollContent, requestedHeight, 448f); peopleScroll.verticalNormalizedPosition = Mathf.Clamp01(_rightScrollPosition); } } private static float BuildMembersTab(RectTransform content) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_0463: Unknown result type (might be due to invalid IL or missing references) //IL_0517: Unknown result type (might be due to invalid IL or missing references) //IL_0510: Unknown result type (might be due to invalid IL or missing references) //IL_05e1: Unknown result type (might be due to invalid IL or missing references) //IL_05da: Unknown result type (might be due to invalid IL or missing references) string effectiveClanId = _snapshot.ClanId; float num = 6f; if (!_snapshot.HasClan) { CreateTopLabel((Transform)(object)content, ClanLocalization.Text("panel_join_or_create_for_roster"), 8f, num + 12f, 396f, 44f, 13, (TextAnchor)4, MutedColor); return num + 64f; } IReadOnlyList readOnlyList = (_snapshot.CanModerate ? _snapshot.Applications.Where((ClanApplicationSummary application) => SearchMatches(application.PlayerName, _playerSearchQuery)).OrderBy((ClanApplicationSummary application) => application.PlayerName, StringComparer.OrdinalIgnoreCase).ToArray() : Array.Empty()); IReadOnlyList readOnlyList2 = (from clanPlayerSummary in _snapshot.Roster where SearchMatches(clanPlayerSummary.Name, _playerSearchQuery) orderby clanPlayerSummary.IsSelf descending, clanPlayerSummary.Role select clanPlayerSummary).ThenBy((ClanPlayerSummary clanPlayerSummary) => clanPlayerSummary.Name, StringComparer.OrdinalIgnoreCase).ToArray(); if (!string.IsNullOrEmpty(_playerSearchQuery) && readOnlyList.Count == 0 && readOnlyList2.Count == 0) { CreateTopLabel((Transform)(object)content, ClanLocalization.Text("panel_no_players_match"), 8f, num + 12f, 396f, 44f, 13, (TextAnchor)4, MutedColor); return num + 64f; } if (readOnlyList.Count > 0) { foreach (ClanApplicationSummary item in readOnlyList) { GameObject obj = CreateTopRow(content, num, 36f, alternate: false); Text obj2 = CreateLabel(obj.transform, item.PlayerName, 7f, 10f, 140f, 16f, 11, (TextAnchor)3, Color.white); obj2.resizeTextForBestFit = true; obj2.resizeTextMinSize = 8; obj2.resizeTextMaxSize = 11; Text obj3 = CreateLabel(obj.transform, ClanLocalization.Text("panel_state_application"), 151f, 10f, 111f, 16f, 9, (TextAnchor)3, MutedColor); obj3.resizeTextForBestFit = true; obj3.resizeTextMinSize = 8; obj3.resizeTextMaxSize = 9; BuildApplicationDecisionButtons(obj.transform, effectiveClanId, item.PlayerId, item.PlayerName, new Rect(266f, 4f, 64f, 28f), new Rect(334f, 4f, 68f, 28f)); num += 40f; } if (readOnlyList2.Count > 0) { num += 4f; } } int num2 = 0; foreach (ClanPlayerSummary member in readOnlyList2) { GameObject val = CreateTopRow(content, num, 36f, num2++ % 2 == 1); Text obj4 = CreateLabel(val.transform, member.Name, 7f, 10f, 140f, 16f, 11, (TextAnchor)3, member.IsSelf ? AccentColor : Color.white); obj4.resizeTextForBestFit = true; obj4.resizeTextMinSize = 8; obj4.resizeTextMaxSize = 11; if (CanAffectMember(_snapshot, member)) { if (_snapshot.IsLeader) { CreateRoleButton(val.transform, member, ClanRole.Officer, ClanLocalization.Role(ClanRole.Officer), 201f, 48f); } CreateRoleButton(val.transform, member, ClanRole.Member, ClanLocalization.Role(ClanRole.Member), 253f, 52f); CreateRoleButton(val.transform, member, ClanRole.Guest, ClanLocalization.Role(ClanRole.Guest), 309f, 46f); } else { CreateLabel(val.transform, ClanLocalization.Role(member.Role), 151f, 7f, 108f, 22f, 10, (TextAnchor)3, MutedColor); } bool flag = _snapshot.IsLeader && !member.IsSelf; if (flag) { ClanRole role = member.Role; bool flag2 = (uint)(role - 1) <= 1u; flag = flag2; } if (flag) { bool flag3 = IsConfirming(ConfirmAction.TransferLeadership, member.Id); ClanUiFeedback.SetTooltip((Selectable)(object)CreateButton(val.transform, flag3 ? ClanLocalization.Text("common_sure_question") : ClanLocalization.Role(ClanRole.Leader), delegate { ConfirmThen(ConfirmAction.TransferLeadership, member.Id, delegate { SendRequest(new ClanRequest { Type = ClanRequestType.TransferLeadership, ClanId = effectiveClanId, TargetId = member.Id }); }); }, 151f, 4f, 46f, 28f, flag3 ? DangerColor : ButtonColor, 8), flag3 ? ClanLocalization.Format("panel_tooltip_confirm_transfer_leadership", member.Name) : ClanLocalization.Format("panel_tooltip_transfer_leadership", member.Name)); } if (CanAffectMember(_snapshot, member)) { bool flag4 = IsConfirming(ConfirmAction.Kick, member.Id); CreateButton(val.transform, flag4 ? ClanLocalization.Text("common_sure_question") : ClanLocalization.Text("panel_action_kick"), delegate { ConfirmThen(ConfirmAction.Kick, member.Id, delegate { SendRequest(new ClanRequest { Type = ClanRequestType.KickPlayer, ClanId = effectiveClanId, TargetId = member.Id }); }); }, 359f, 4f, 43f, 28f, flag4 ? DangerColor : ButtonColor, 9); } num += 40f; } return num + 2f; } private static void CreateRoleButton(Transform parent, ClanPlayerSummary member, ClanRole role, string label, float x, float width) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) string effectiveClanId = _snapshot.ClanId; if (ClanDataRules.GetRolePower(_snapshot.SelfRole) > ClanDataRules.GetRolePower(role)) { bool flag = member.Role == role; Button val = CreateButton(parent, label, delegate { SendRequest(new ClanRequest { Type = ClanRequestType.SetRole, ClanId = effectiveClanId, TargetId = member.Id, Role = role }); }, x, 4f, width, 28f, flag ? ActiveButtonColor : InactiveToggleButtonColor, 8); SetButtonLabelColor(val, flag ? Color.white : InactiveButtonLabelColor); ClanUiFeedback.SetTooltip((Selectable)(object)val, flag ? ClanLocalization.Format("panel_tooltip_role_already_set", member.Name, ClanLocalization.Role(role)) : ClanLocalization.Format("panel_tooltip_set_role", member.Name, ClanLocalization.Role(role))); if (flag) { ColorBlock colors = ((Selectable)val).colors; ((ColorBlock)(ref colors)).disabledColor = ActiveButtonColor; ((Selectable)val).colors = colors; ((Selectable)val).interactable = false; } } } private static float BuildPlayersTab(RectTransform content) { //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) string effectiveClanId = _snapshot.ClanId; float num = 6f; IReadOnlyList players = _directory.Players; IReadOnlyList readOnlyList = (from clanDirectoryPlayerSummary in players where SearchMatches(clanDirectoryPlayerSummary.PlayerName, _playerSearchQuery) || SearchMatches(clanDirectoryPlayerSummary.ClanName, _playerSearchQuery) orderby clanDirectoryPlayerSummary.IsSelf descending, clanDirectoryPlayerSummary.CanResolveApplication descending, clanDirectoryPlayerSummary.IsOnline descending, clanDirectoryPlayerSummary.LastSeenUtcTicks descending select clanDirectoryPlayerSummary).ThenBy((ClanDirectoryPlayerSummary clanDirectoryPlayerSummary) => clanDirectoryPlayerSummary.PlayerName, StringComparer.OrdinalIgnoreCase).ToArray(); if (readOnlyList.Count == 0) { string text = ((players.Count > 0 && !string.IsNullOrEmpty(_playerSearchQuery)) ? ClanLocalization.Text("panel_no_players_or_clans_match") : (ClanRpc.IsDirectoryRequestPending ? ClanLocalization.Text("panel_loading_recent_players") : ClanLocalization.Text("panel_no_recent_players"))); CreateTopLabel((Transform)(object)content, text, 8f, num + 12f, 396f, 44f, 13, (TextAnchor)4, MutedColor); return num + 64f; } int num2 = 0; foreach (ClanDirectoryPlayerSummary player in readOnlyList) { GameObject val = CreateTopRow(content, num, 36f, num2++ % 2 == 1); Text obj = CreateLabel(val.transform, player.PlayerName, 7f, 10f, 140f, 16f, 11, (TextAnchor)3, player.IsSelf ? AccentColor : Color.white); obj.resizeTextForBestFit = true; obj.resizeTextMinSize = 8; obj.resizeTextMaxSize = 11; Text obj2 = CreateLabel(val.transform, DirectoryStateText(player), 151f, 10f, 111f, 16f, 10, (TextAnchor)3, DirectoryStateColor(player.State)); obj2.resizeTextForBestFit = true; obj2.resizeTextMinSize = 8; obj2.resizeTextMaxSize = 10; Text obj3 = CreateLabel(val.transform, LastSeenText(player), 266f, 7f, 64f, 21f, 10, (TextAnchor)5, player.IsOnline ? ClanUiFactory.GetClanColor() : MutedColor); obj3.resizeTextForBestFit = true; obj3.resizeTextMinSize = 8; obj3.resizeTextMaxSize = 10; if (_snapshot.CanModerate && player.CanInvite) { Button obj4 = CreateButton(val.transform, ClanLocalization.Text("panel_action_invite"), delegate { SendRequest(new ClanRequest { Type = ClanRequestType.Invite, ClanId = effectiveClanId, TargetId = player.PlayerId }); }, 334f, 4f, 68f, 28f, ButtonColor, 9); ((Object)obj4).name = "InvitePlayer"; ClanUiFeedback.SetTooltip((Selectable)(object)obj4, ClanLocalization.Format("panel_tooltip_invite_player", player.PlayerName)); } num += 40f; } return num + 2f; } private static void BuildApplicationDecisionButtons(Transform parent, string clanId, string playerId, string playerName, Rect acceptBounds, Rect declineBounds) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) Button obj = CreateButton(parent, ClanLocalization.Text("panel_action_accept"), delegate { SendRequest(new ClanRequest { Type = ClanRequestType.AcceptApplication, ClanId = clanId, TargetId = playerId }); }, ((Rect)(ref acceptBounds)).x, ((Rect)(ref acceptBounds)).y, ((Rect)(ref acceptBounds)).width, ((Rect)(ref acceptBounds)).height, ButtonColor, 9); ((Object)obj).name = "AcceptApplication"; ClanUiFeedback.SetTooltip((Selectable)(object)obj, ClanLocalization.Format("panel_tooltip_accept_application", playerName)); bool flag = IsConfirming(ConfirmAction.RejectApplication, playerId); Button obj2 = CreateButton(parent, flag ? ClanLocalization.Text("panel_action_decline_question") : ClanLocalization.Text("panel_action_decline"), delegate { ConfirmThen(ConfirmAction.RejectApplication, playerId, delegate { SendRequest(new ClanRequest { Type = ClanRequestType.RejectApplication, ClanId = clanId, TargetId = playerId }); }); }, ((Rect)(ref declineBounds)).x, ((Rect)(ref declineBounds)).y, ((Rect)(ref declineBounds)).width, ((Rect)(ref declineBounds)).height, flag ? DangerColor : ButtonColor, 9); ((Object)obj2).name = "DeclineApplication"; ClanUiFeedback.SetTooltip((Selectable)(object)obj2, flag ? ClanLocalization.Format("panel_tooltip_confirm_decline_application", playerName) : ClanLocalization.Format("panel_tooltip_decline_application", playerName)); } private static void BuildEditor(Transform parent) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) Selectable[] componentsInChildren = _root.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].interactable = false; } GameObject val = CreateRect("ProfileEditorBlocker", parent, 0f, 0f, 790f, 520f, typeof(Image)); ((Graphic)val.GetComponent()).color = new Color(0f, 0f, 0f, 0.62f); ((Graphic)val.GetComponent()).raycastTarget = true; GameObject val2 = CreateWoodPanelObject("ProfileEditor", val.transform, 650f, 470f, draggable: false); Place(val2.GetComponent(), 70f, 25f, 650f, 470f); ((Graphic)val2.GetComponent()).raycastTarget = true; CreateLabel(val2.transform, _editorCreating ? ClanLocalization.Text("panel_editor_make_clan") : ClanLocalization.Format("panel_editor_edit_clan", _snapshot.ClanName), 14f, 426f, 420f, 32f, 18, (TextAnchor)3, AccentColor); Button val3 = CreateButton(val2.transform, _editorSubmissionPending ? ClanLocalization.Text("panel_editor_sending") : (_editorCreating ? ClanLocalization.Text("panel_editor_create_clan") : ClanLocalization.Text("panel_editor_save_profile")), SubmitEditor, 448f, 426f, 138f, 32f, ActiveButtonColor, 12); ((Selectable)val3).interactable = !_editorSubmissionPending; if (_editorSubmissionPending) { SetButtonLabelColor(val3, DisabledButtonLabelColor); } CreateButton(val2.transform, "×", CloseEditor, 594f, 426f, 40f, 32f, ButtonColor, 19); CreateLabel(val2.transform, ClanLocalization.Text("panel_editor_clan_name"), 14f, 394f, 180f, 24f, 12, (TextAnchor)3, MutedColor); InputField obj = CreateInputField(val2.transform, "ClanName", _draftName, ClanLocalization.Text("panel_editor_name_placeholder"), 14f, 358f, 620f, 34f, 80); ((Selectable)obj).interactable = !_editorSubmissionPending; ((UnityEvent)(object)obj.onValueChanged).AddListener((UnityAction)delegate(string value) { _draftName = value; ClearEditorError(); }); CreateLabel(val2.transform, ClanLocalization.Text("panel_editor_description"), 14f, 330f, 180f, 24f, 12, (TextAnchor)3, MutedColor); InputField obj2 = CreateInputField(val2.transform, "ClanDescription", _draftDescription, ClanLocalization.Text("panel_editor_description_placeholder"), 14f, 254f, 620f, 70f, 160); ((Selectable)obj2).interactable = !_editorSubmissionPending; ((UnityEvent)(object)obj2.onValueChanged).AddListener((UnityAction)delegate(string value) { _draftDescription = value; ClearEditorError(); }); CreateLabel(val2.transform, ClanLocalization.Text("panel_editor_emblem"), 14f, 226f, 260f, 24f, 12, (TextAnchor)3, MutedColor); BuildEditorEmblemPicker(val2.transform); BuildEditorErrorBanner(val2.transform); } private static void BuildEditorEmblemPicker(Transform parent) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) RectTransform content; ScrollRect val = CreateScrollView(parent, "EmblemPicker", 14f, 14f, 620f, 206f, out content, _emblemScrollPosition, delegate(float value) { _emblemScrollPosition = value; }); ((Behaviour)val).enabled = !_editorSubmissionPending; IReadOnlyList emblemPickerItems = ClanEmoji.GetEmblemPickerItems(); int num = emblemPickerItems.Count + 1; bool flag = string.IsNullOrWhiteSpace(_draftEmblemKey); Button obj = CreateTopButton((Transform)(object)content, ClanLocalization.Text("common_none"), delegate { SelectDraftEmblem(""); }, 5f, 5f, 58f, 58f, flag ? ActivePickerButtonColor : PickerButtonColor, 10); RemoveButtonFrame(obj); SetPickerButtonState(obj, flag, !_editorSubmissionPending); ClanUiFeedback.SetTooltip((Selectable)(object)obj, ClanLocalization.Text("panel_tooltip_no_emblem")); for (int num2 = 0; num2 < emblemPickerItems.Count; num2++) { ClanEmoji.ClanEmblemPickerItem emblem = emblemPickerItems[num2]; int num3 = num2 + 1; int num4 = num3 % 9; int num5 = num3 / 9; bool flag2 = StringComparer.Ordinal.Equals(_draftEmblemKey, emblem.Name); Button obj2 = CreateTopButton((Transform)(object)content, "", delegate { SelectDraftEmblem(emblem.Name); }, 5f + (float)num4 * 64f, 5f + (float)num5 * 64f, 58f, 58f, flag2 ? ActivePickerButtonColor : PickerButtonColor, 10); RemoveButtonFrame(obj2); AddSprite(((Component)obj2).transform, emblem.Sprite, 7f, 7f, 44f, 44f); SetPickerButtonState(obj2, flag2, !_editorSubmissionPending); } int num6 = Math.Max(1, (num + 9 - 1) / 9); SetScrollContentHeight(val, content, 10f + (float)num6 * 64f, 206f); val.verticalNormalizedPosition = Mathf.Clamp01(_emblemScrollPosition); } private static void BuildEditorErrorBanner(Transform parent) { //IL_0056: 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) _editorErrorBanner = CreateRect("EditorError", parent, 14f, 14f, 620f, 36f, typeof(Image)); Image component = _editorErrorBanner.GetComponent(); ((Graphic)component).color = new Color(0.38f, 0.1f, 0.08f, 0.96f); ((Graphic)component).raycastTarget = false; _editorErrorLabel = CreateLabel(_editorErrorBanner.transform, _editorError, 10f, 2f, 600f, 32f, 11, (TextAnchor)3, Color.white); _editorErrorLabel.horizontalOverflow = (HorizontalWrapMode)0; _editorErrorLabel.verticalOverflow = (VerticalWrapMode)0; _editorErrorBanner.SetActive(!string.IsNullOrWhiteSpace(_editorError)); } private static void OpenCreateEditor() { ClearConfirmation(); _editorOpen = true; _editorCreating = true; ClearEditorSubmission(); ClearEditorError(); _draftName = ""; _draftDescription = ""; _draftEmblemKey = ""; RebuildView(); } private static void OpenEditEditor() { if (!_snapshot.IsLeader) { ClanRpc.NotifyStatus(ClanLocalization.Text("panel_error_leader_only_edit")); return; } ClearConfirmation(); _editorOpen = true; _editorCreating = false; ClearEditorSubmission(); ClearEditorError(); _draftName = _snapshot.ClanName; _draftDescription = _snapshot.ClanDescription; _draftEmblemKey = _snapshot.ClanEmblemKey; RebuildView(); } private static void CloseEditor() { _editorOpen = false; ClearEditorSubmission(); ClearEditorError(); RebuildView(); } private static void SubmitEditor() { if (_editorSubmissionPending) { return; } ClearEditorError(); try { string draftName = ClanDataRules.RequireClanName(_draftName); string draftDescription = ClanDataRules.RequireClanDescription((_draftDescription ?? "").Replace('\r', ' ').Replace('\n', ' ')); string text = ClanDataRules.RequireClanEmblemKey(_draftEmblemKey); if (!ClanEmoji.IsAvailableEmblemKey(text)) { ShowEditorError(ClanLocalization.Text("panel_error_select_available_emblem")); return; } _draftName = draftName; _draftDescription = draftDescription; _draftEmblemKey = text; ClanRequest clanRequest; if (_editorCreating) { if (_snapshot.HasAnyClan) { ShowEditorError(ClanLocalization.Text("panel_error_leave_before_create")); return; } clanRequest = new ClanRequest { Type = ClanRequestType.CreateClan, ClanName = _draftName, Description = _draftDescription, EmblemKey = _draftEmblemKey }; } else { if (!_snapshot.IsLeader) { ShowEditorError(ClanLocalization.Text("panel_error_leader_only_edit")); return; } if (SubmittedProfileMatches(_snapshot)) { _editorOpen = false; ClearEditorSubmission(); ClearEditorError(); RebuildView(); return; } clanRequest = new ClanRequest { Type = ClanRequestType.UpdateClanProfile, ClanId = _snapshot.ClanId, ClanName = _draftName, Description = _draftDescription, EmblemKey = _draftEmblemKey }; } clanRequest.RequestId = ClanRpc.NextRequestId(); _editorSubmissionRequestId = clanRequest.RequestId; _editorSubmissionPending = true; RebuildView(); if (!ClanRpc.Send(clanRequest)) { ClearEditorSubmission(); ShowEditorError(ClanLocalization.Text("panel_error_server_not_connected")); RebuildView(); } else { ScheduleDirectoryRefresh(); } } catch (InvalidDataException exception) { ClearEditorSubmission(); ShowEditorError(LocalizeEditorValidationError(exception)); } } private static void ClearEditorSubmission() { _editorSubmissionPending = false; _editorSubmissionRequestId = 0L; } private static string LocalizeEditorValidationError(InvalidDataException exception) { if (!ClanDataRules.TryGetValidationError(exception, out var field, out var error)) { return ClanLocalization.Text("panel_error_invalid_profile"); } switch (field) { case ClanValidationField.ClanName: switch (error) { case ClanValidationError.Required: return ClanLocalization.Text("panel_error_name_required"); case ClanValidationError.InvalidUnicode: return ClanLocalization.Text("panel_error_name_invalid_unicode"); case ClanValidationError.InvalidCharacters: return ClanLocalization.Text("panel_error_name_invalid_characters"); case ClanValidationError.TooLong: return ClanLocalization.Format("panel_error_name_too_long", 40); case ClanValidationError.TrailingSeparator: return ClanLocalization.Text("panel_error_name_trailing_separator"); case ClanValidationError.MissingLetterOrNumber: return ClanLocalization.Text("panel_error_name_letter_number"); } break; case ClanValidationField.ClanDescription: switch (error) { case ClanValidationError.TooLong: return ClanLocalization.Format("panel_error_description_too_long", 160); case ClanValidationError.RichText: return ClanLocalization.Text("panel_error_description_rich_text"); case ClanValidationError.ControlCharacters: return ClanLocalization.Text("panel_error_description_control_characters"); } break; case ClanValidationField.ClanEmblemKey: switch (error) { case ClanValidationError.TooLong: return ClanLocalization.Format("panel_error_emblem_too_long", 32); case ClanValidationError.RichText: return ClanLocalization.Text("panel_error_emblem_rich_text"); case ClanValidationError.ControlCharacters: return ClanLocalization.Text("panel_error_emblem_control_characters"); case ClanValidationError.InvalidStartOrEnd: return ClanLocalization.Text("panel_error_emblem_start_end"); case ClanValidationError.InvalidCharacters: return ClanLocalization.Text("panel_error_emblem_invalid_characters"); } break; } return ClanLocalization.Text("panel_error_invalid_profile"); } private static void ShowEditorError(string message) { _editorError = ClanUiFactory.CleanSingleLine(message); if ((Object)(object)_editorErrorLabel != (Object)null) { _editorErrorLabel.text = _editorError; } if ((Object)(object)_editorErrorBanner != (Object)null) { _editorErrorBanner.SetActive(_editorError.Length > 0); } } private static void ClearEditorError() { _editorError = ""; if ((Object)(object)_editorErrorLabel != (Object)null) { _editorErrorLabel.text = ""; } if ((Object)(object)_editorErrorBanner != (Object)null) { _editorErrorBanner.SetActive(false); } } private static bool SubmittedProfileMatches(ClanClientSnapshot snapshot) { if (snapshot.HasClan && StringComparer.Ordinal.Equals(snapshot.ClanName, _draftName) && StringComparer.Ordinal.Equals(snapshot.ClanDescription, _draftDescription)) { return StringComparer.Ordinal.Equals(snapshot.ClanEmblemKey, _draftEmblemKey); } return false; } private static void SelectDraftEmblem(string emblemKey) { if (!_editorSubmissionPending) { _draftEmblemKey = emblemKey ?? ""; ClearEditorError(); RebuildView(); } } private static void SelectTab(PanelTab tab) { if (_tab != tab) { _tab = tab; _rightScrollPosition = 1f; ClearConfirmation(); RefreshHeaderState(); PopulatePeopleRows(); if (_tab == PanelTab.Players && _directory.RequestId <= 0) { ClanRpc.RequestDirectory(); } } } private static void OnClanSearchChanged(string value) { string text = NormalizeSearchQuery(value); if (!StringComparer.Ordinal.Equals(_clanSearchQuery, text)) { _clanSearchQuery = text; _clanScrollPosition = 1f; ClearConfirmation(); _clanRowsDirty = true; } } private static void OnPlayerSearchChanged(string value) { string text = NormalizeSearchQuery(value); if (!StringComparer.Ordinal.Equals(_playerSearchQuery, text)) { _playerSearchQuery = text; _rightScrollPosition = 1f; ClearConfirmation(); _peopleRowsDirty = true; } } private static string NormalizeSearchQuery(string value) { return (value ?? "").Trim(); } private static bool MatchesClanSearch(ClanPublicSummary clan) { if (!SearchMatches(clan.Name, _clanSearchQuery) && !SearchMatches(clan.LeaderName, _clanSearchQuery)) { return SearchMatches(clan.Description, _clanSearchQuery); } return true; } private static bool SearchMatches(string? value, string query) { if (!string.IsNullOrEmpty(query)) { return (value ?? "").IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static void ResetSearchState() { bool num = !string.IsNullOrEmpty(_clanSearchQuery) || !string.IsNullOrEmpty(_playerSearchQuery); _clanSearchQuery = ""; _playerSearchQuery = ""; _clanScrollPosition = 1f; _rightScrollPosition = 1f; if ((Object)(object)_clanSearchInput != (Object)null) { _clanSearchInput.SetTextWithoutNotify(""); } if ((Object)(object)_playerSearchInput != (Object)null) { _playerSearchInput.SetTextWithoutNotify(""); } if (num) { _clanRowsDirty = true; _peopleRowsDirty = true; } } private static void ApplyToClan(string clanId) { SendRequest(new ClanRequest { Type = ClanRequestType.Apply, ClanId = clanId }); } private static void SendRequest(ClanRequest request) { ClearConfirmation(); if (ClanRpc.Send(request)) { ScheduleDirectoryRefresh(); } } private static void ScheduleDirectoryRefresh() { _directoryRefreshAt = Mathf.Min(_directoryRefreshAt, Time.unscaledTime + 2.1f); } private static void ConfirmThen(ConfirmAction action, string target, Action confirmed) { if (IsConfirming(action, target)) { ClearConfirmation(); if (IsOpen) { RebuildView(); } confirmed(); } else { _confirmAction = action; _confirmTarget = target ?? ""; RebuildView(); } } private static bool IsConfirming(ConfirmAction action, string target) { if (_confirmAction == action) { return StringComparer.Ordinal.Equals(_confirmTarget, target ?? ""); } return false; } private static void ClearConfirmation() { _confirmAction = ConfirmAction.None; _confirmTarget = ""; } private static bool CanAffectMember(ClanClientSnapshot snapshot, ClanPlayerSummary member) { if (snapshot.CanModerate && !member.IsSelf) { return ClanDataRules.GetRolePower(snapshot.SelfRole) > ClanDataRules.GetRolePower(member.Role); } return false; } private static string DirectoryStateText(ClanDirectoryPlayerSummary player) { return player.State switch { ClanDirectoryPlayerState.Clan => string.IsNullOrWhiteSpace(player.ClanName) ? ClanLocalization.Text("panel_state_clan") : player.ClanName, ClanDirectoryPlayerState.Pending => ClanLocalization.Text("panel_state_pending_lower"), ClanDirectoryPlayerState.Invited => ClanLocalization.Text("panel_state_invited"), _ => "-", }; } private static Color DirectoryStateColor(ClanDirectoryPlayerState state) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) return (Color)(state switch { ClanDirectoryPlayerState.Pending => AccentColor, ClanDirectoryPlayerState.Invited => new Color(0.56f, 0.78f, 1f, 1f), ClanDirectoryPlayerState.Clan => Color.white, _ => MutedColor, }); } private static string LastSeenText(ClanDirectoryPlayerSummary player) { if (player.IsOnline) { return ClanLocalization.Text("panel_last_seen_online"); } if (player.LastSeenUtcTicks <= 0) { return ClanLocalization.Text("panel_last_seen_unknown"); } TimeSpan timeSpan = DateTime.UtcNow - new DateTime(player.LastSeenUtcTicks, DateTimeKind.Utc); if (timeSpan.TotalHours < 24.0) { return ClanLocalization.Text("panel_last_seen_today"); } return ClanLocalization.Format("panel_last_seen_days", Mathf.Clamp((int)timeSpan.TotalDays, 1, 28)); } private static GameObject CreateTopRow(RectTransform content, float top, float height, bool alternate) { //IL_0045: 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) GameObject obj = ClanUiFactory.CreateObject("Row", (Transform)(object)content, typeof(Image)); PlaceTop(obj.GetComponent(), 3f, top, 406f, height); ((Graphic)obj.GetComponent()).color = (alternate ? AlternateRowColor : RowColor); return obj; } private static ScrollRect CreateScrollView(Transform parent, string name, float x, float y, float width, float height, out RectTransform content, float normalizedPosition, Action rememberPosition, float scrollbarTopInset = 0f) { //IL_0057: 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_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) GameObject val = CreateRect(name, parent, x, y, width, height, typeof(Image), typeof(ScrollRect)); Image component = val.GetComponent(); ((Graphic)component).color = new Color(0f, 0f, 0f, 0.1f); ((Graphic)component).raycastTarget = true; GameObject val2 = CreateRect("Viewport", val.transform, 0f, 0f, width, height, typeof(Image), typeof(Mask)); Image component2 = val2.GetComponent(); ((Graphic)component2).color = Color.white; ((Graphic)component2).raycastTarget = true; val2.GetComponent().showMaskGraphic = false; GameObject val3 = ClanUiFactory.CreateObject("Content", val2.transform); content = val3.GetComponent(); content.anchorMin = new Vector2(0f, 1f); content.anchorMax = new Vector2(1f, 1f); content.pivot = new Vector2(0.5f, 1f); content.anchoredPosition = Vector2.zero; content.sizeDelta = new Vector2(0f, height); ScrollRect component3 = val.GetComponent(); component3.content = content; component3.viewport = val2.GetComponent(); component3.horizontal = false; component3.vertical = true; component3.inertia = true; component3.decelerationRate = 0.12f; ClanUiFactory.ConfigureVerticalScroll(component3, hasOverflow: true); component3.verticalScrollbar = null; GameObject val4 = ClanUiFactory.CreateObject("Scrollbar", val.transform, typeof(Image), typeof(Scrollbar)); RectTransform component4 = val4.GetComponent(); component4.anchorMin = new Vector2(1f, 0f); component4.anchorMax = new Vector2(1f, 1f); component4.pivot = new Vector2(1f, 0.5f); component4.offsetMin = new Vector2(-2f, 0f); component4.offsetMax = new Vector2(0f, 0f - Mathf.Clamp(scrollbarTopInset, 0f, height)); ((Graphic)val4.GetComponent()).color = new Color(0f, 0f, 0f, 0.42f); GameObject obj = ClanUiFactory.CreateObject("Handle", val4.transform, typeof(Image)); RectTransform component5 = obj.GetComponent(); component5.anchorMin = Vector2.zero; component5.anchorMax = Vector2.one; component5.offsetMin = Vector2.zero; component5.offsetMax = Vector2.zero; Image component6 = obj.GetComponent(); ((Graphic)component6).color = new Color(1f, 0.63f, 0.24f, 0.96f); Scrollbar component7 = val4.GetComponent(); component7.handleRect = component5; ((Selectable)component7).targetGraphic = (Graphic)(object)component6; component7.direction = (Direction)2; ((Selectable)component7).transition = (Transition)0; component7.value = Mathf.Clamp01(normalizedPosition); component3.verticalScrollbar = component7; component3.verticalScrollbarVisibility = (ScrollbarVisibility)0; component3.verticalScrollbarSpacing = 0f; component3.verticalNormalizedPosition = Mathf.Clamp01(normalizedPosition); ((UnityEvent)(object)component3.onValueChanged).AddListener((UnityAction)delegate(Vector2 value) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) rememberPosition(Mathf.Clamp01(value.y)); }); return component3; } private static void SetScrollContentHeight(ScrollRect scroll, RectTransform content, float requestedHeight, float viewportHeight) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) content.sizeDelta = new Vector2(0f, Mathf.Max(viewportHeight, requestedHeight)); bool hasOverflow = requestedHeight > viewportHeight + 0.5f; ClanUiFactory.ConfigureVerticalScroll(scroll, hasOverflow); } private static Button CreateTopButton(Transform parent, string text, Action action, float x, float top, float width, float height, Color color, int fontSize) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) Button obj = CreateButtonObject(parent, text, action, color, fontSize); PlaceTop(((Component)obj).GetComponent(), x, top, width, height); return obj; } private static Text CreateTopLabel(Transform parent, string text, float x, float top, float width, float height, int fontSize, TextAnchor alignment, Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Text obj = CreateLabelObject(parent, text, fontSize, alignment, color); PlaceTop(((Component)obj).GetComponent(), x, top, width, height); return obj; } private static Button CreateButton(Transform parent, string text, Action action, float x, float y, float width, float height, Color color, int fontSize) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) Button obj = CreateButtonObject(parent, text, action, color, fontSize); Place(((Component)obj).GetComponent(), x, y, width, height); return obj; } private static Button CreateButtonObject(Transform parent, string text, Action action, Color color, int fontSize) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: 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) GameObject obj = ClanUiFactory.CreateObject("Button", parent, typeof(Image), typeof(Button)); Image component = obj.GetComponent(); ((Graphic)component).color = color; ((Graphic)component).raycastTarget = true; Button component2 = obj.GetComponent